Table of Contents
When integrating with external APIs in Laravel, catching generic exceptions and retrying blindly wastes queue resources. Here’s a better approach: parse error responses and throw domain-specific exceptions.
The Problem
Your queue job calls an API. Sometimes it returns 500 Internal Server Error (temporary). Sometimes it returns 400 Bad Request - Invalid Resource ID (permanent config error). If you catch both with a generic exception, the queue retries forever even for permanent failures.
try {
$data = $this->apiClient->fetchResource($resourceId);
} catch (ApiException $exception) {
// Generic catch = queue retries uselessly for config errors
throw $exception;
}
The Solution
Parse the API error response and throw appropriate exceptions:
try {
$data = $this->apiClient->fetchResource($resourceId);
} catch (ApiException $exception) {
$errorCode = json_decode(
(string)$exception->getPrevious()?->getResponse()->getBody(),
true
)['error'] ?? null;
if ($errorCode === 'INTERNAL_SERVER_ERROR') {
// Temporary failure - queue should retry
throw new TemporarilyUnavailableException(
'Cannot fetch data',
500,
$exception
);
}
if ($errorCode === 'INVALID_RESOURCE_ID') {
// Permanent config error - stop retrying immediately
throw new InvalidMappingException(
$this->getResourceMapping($resourceId),
previous: $exception
);
}
// Unknown error - rethrow original
throw $exception;
}
Why This Works
Different exception types let your queue orchestrator handle them appropriately:
- TemporarilyUnavailableException โ Queue retries with backoff (API might recover)
- InvalidMappingException โ Queue deletes job immediately (config needs manual fix)
- Generic exceptions โ Default retry behavior
In one real-world case, this pattern stopped ~20,900 useless retry attempts for permanent mapping errors, freeing queue resources immediately.
Key Takeaway
Don’t treat all API failures the same. Parse error responses, throw specific exceptions, and let your queue orchestrator make smart retry decisions.
Leave a Reply