Laravel 9 HTTP client exception handling

Stefan Bogdanescu

Founder & Senior Architect · 2026-06-29

Laravel Company

Mastering Laravel HTTP Client Exception Handling: Beyond Basic try-catch

As a senior developer working with modern PHP frameworks like Laravel, dealing with external HTTP requests is a daily necessity. When these requests fail—due to network issues, DNS errors, or server-side application errors—exception handling becomes crucial. Many developers struggle with the Laravel HTTP Client (Illuminate\Http\Client) because it blurs the line between catching true connection failures and handling unsuccessful HTTP status codes (like 404 or 500).

The issue you are facing is common: trying to catch a specific exception like ConnectionException doesn't always cover all failure scenarios, especially when dealing with complex response chains. Let’s dive into what is happening under the hood and establish the best practices for robust HTTP client error management in Laravel.

Understanding Where Exceptions Occur

The Laravel HTTP Client throws exceptions primarily when there is a genuine failure to establish or maintain the connection. This typically includes scenarios like:

  1. DNS Resolution Failure: The domain does not exist (as seen in your example).
  2. Connection Timeout: The server takes too long to respond.
  3. Network Unavailability: The client cannot reach the destination.

However, when the request successfully reaches the server, but the server returns an error status code (e.g., 400 Bad Request or 505 HTTP Gateway Timeout), the HTTP Client does not throw an exception by default. Instead, it returns a Response object where $response->failed() will be true. This is the crucial distinction you need to master.

Differentiating Exceptions from Response Failures

Your attempts to catch generic exceptions often fail because the failure mode isn't always an exception; sometimes it’s encapsulated within the successful response object.

1. Catching Network/Connection Errors

For true connectivity issues, catching exceptions is appropriate. The most relevant base class for these operational failures related to HTTP requests is often Illuminate\Http\Client\RequestException or its parent classes.

use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\Client\RequestException;

try {
    $response = Http::get('domain-is-not-exist.com');
} catch (ConnectionException $e) {
    // This catches true network failures (DNS, connection refused)
    dd("Connection Error: Could not reach the server.");
} catch (RequestException $e) {
    // This catches errors thrown by the client itself during request execution
    dd("HTTP Request Exception: An error occurred during the request lifecycle.");
} catch (\Exception $e) {
    // Catch any other unforeseen errors
    dd("An unexpected error occurred: " . $e->getMessage());
}

2. Handling Application-Level Failures (Status Codes)

When you receive a response, regardless of whether it's successful or an error code (like 500), the first step should be checking the response object itself before attempting to throw further exceptions. This is where methods like failed(), serverError(), and throw() come into play.

The method $response->throw() is incredibly useful. If you call this method on a failed response, it forces Laravel to throw an Illuminate\Http\Client\RequestException with the appropriate status code, allowing your outer try-catch block to handle it consistently.

$response = Http::get($url);

if ($response->failed()) {
    // If the response failed (4xx or 5xx), throw an exception immediately
    throw new \Exception("API Call Failed with status: " . $response->status());
}

// Or, if you want to handle 500 errors specifically:
if ($response->serverError()) {
    throw new \Exception("Server Error encountered.");
}

// If everything passed, proceed normally
$data = $response->json();

Best Practice Summary

For robust HTTP client error handling in Laravel, adopt a layered approach:

  1. Layer 1 (Network Check): Wrap your call in a try-catch block to catch low-level connection exceptions (ConnectionException).
  2. Layer 2 (Response Check): Immediately after receiving the response, check $response->failed() or $response->successful(). Throw an application-specific exception if the status code indicates failure.
  3. Layer 3 (Final Handling): Use a final catch block to handle both network errors and your custom application errors gracefully.

By combining specific exception catching with explicit response validation, you ensure that whether the error is due to a broken connection or an application-level response code, your code handles it correctly. For deeper insights into Laravel's HTTP features and dependency management, always refer back to the official documentation at laravelcompany.com.

This layered strategy ensures that your application remains predictable and resilient when dealing with external services, making your code significantly more robust than relying on a single, broad catch (Exception $e).