How to catch 500 error in Controller using Laravel
Stefan Bogdanescu
Founder & Senior Architect · 2026-06-29
How to Catch 500 Errors in Controller When Calling External APIs in Laravel
Integrating external APIs is a cornerstone of modern application development. In a Laravel environment, when you rely on an external service, robust error handling is not just a nice-to-have; it is essential for building resilient and production-ready systems. As a senior developer, I often see developers struggle with catching specific HTTP error codes—especially the dreaded 500 Internal Server Error—when making Guzzle requests.
The scenario you presented perfectly illustrates this challenge: attempting to catch only client-side errors while ignoring server-side failures leads to unexpected crashes or poor user experiences. Let’s dive into why your initial approach failed and how to implement comprehensive error handling in Laravel.
The Pitfall of Insufficient Error Catching
You are correctly using a try...catch block, which is the right starting point for handling network issues. However, the failure stems from understanding the specific exceptions thrown by Guzzle based on the HTTP response status code.
When an external API returns a 500 Internal Server Error, Guzzle does not throw a standard ClientException (which typically handles 4xx errors like 401 or 404). Instead, it throws a more specific exception: GuzzleHttp\Exception\ServerException. If you only catch ClientException, the ServerException remains unhandled, leading to the error being logged by PHP and potentially crashing your request flow if not properly managed.
As shown in your log, the issue is clearly identified:
GuzzleHttp\Exception\ServerException: Server error: `POST https://api.example.co.uk/Book` resulted in a `500 Internal Server Error` response:
This confirms that the exception thrown is related to server-side issues, not client-side issues.
The Solution: Handling Specific Guzzle Exceptions
To correctly handle external API failures, you must explicitly catch both client errors (4xx) and server errors (5xx). This allows you to differentiate between a request that failed due to bad input (client error) and one that failed due to the external service being unavailable or encountering an internal issue (server error).
Here is how you can refactor your controller logic to handle both scenarios gracefully:
Refactored Code Example
We will update your code to explicitly catch ClientException for 4xx errors and ServerException for 5xx errors, allowing tailored responses for each case.
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\ServerException;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Log; // Using Laravel logging instead of raw file writing
// ... inside your controller method
try {
$res4 = $client3->post('https://api.example.co.uk/Book', [
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'Authorization' => 'Bearer ajhsdbjhasdbasdbasd',
],
'json' => [
'custFirstName' => $FirstName,
'custLastName' => $Surname,
'custPhone' => $Mobile,
'custEmail' => $Email,
]
]);
// If successful (2xx response)
$data = json_decode($res4->getBody()->getContents(), true);
// Process successful data...
} catch (ClientException $e) {
// Handle 4xx errors (e.g., Bad Request, Unauthorized)
$response = $e->getResponse();
$errorData = json_decode($response->getBody()->getContents());
Log::warning("API Client Error: " . $response->getStatusCode(), ['response' => $response->getBody()->getContents()]);
// Return a specific error response to the user
return response()->json(['error' => 'Invalid request parameters'], 400);
} catch (ServerException $e) {
// Handle 5xx errors (e.g., 500 Internal Server Error)
$response = $e->getResponse();
Log::error("API Server Error: " . $response->getStatusCode(), ['response' => $response->getBody()->getContents()]);
// Notify support or handle system failure gracefully
Mail::raw('External API Server Failure', function ($message) {
$message->to('support@example.com')->subject('CRITICAL: External API 500 Error');
});
// Return a generic server error to the user
return response()->json(['error' => 'The external service is currently unavailable.'], 503);
} catch (RequestException $e) {
// Handle general network issues or connection timeouts
Log::error("Network or Connection Error: " . $e->getMessage());
return response()->json(['error' => 'Could not connect to the external service.'], 500);
}
Best Practices for Resilient API Integration
Integrating third-party services requires moving beyond simple try...catch blocks. To truly build robust applications in Laravel, follow these principles:
- Specific Exception Handling: Always catch the specific exceptions thrown by the HTTP client library (like Guzzle). Differentiate between
ClientException(client error) andServerException(server error). - Leverage Laravel Logging: Instead of manually writing to a log file, use Laravel's built-in
Logfacade. This ensures your errors are stored in the centralized logging system, making debugging much easier—a core principle when building structured applications like those promoted by frameworks such as Laravel. - Return Appropriate HTTP Codes: Based on the error type you catch, return the correct HTTP status code to the client (e.g., 400 for bad input, 503 for external service unavailability). This adheres to RESTful principles.
- Consider Service Layers: For complex applications, abstract the API calls into dedicated Service classes. This keeps your Controllers clean and allows you to centralize all HTTP error handling in one place, promoting better separation of concerns.
Conclusion
Handling external API errors effectively requires moving beyond generic exception catching. By understanding the specific exceptions thrown by tools like Guzzle—differentiating between ClientException (4xx) and ServerException (5xx)—you can build middleware that provides meaningful feedback to both your application and your end-users. Implementing this structured approach ensures that when external dependencies fail, your Laravel application remains stable, informative, and resilient.