How to catch "too many attempt" exception in a middleware Laravel 5

Stefan Bogdanescu

Founder & Senior Architect · 2026-06-29

Laravel Company

Mastering Exception Handling in Laravel Middleware: Catching Layered Errors

As a senior developer working with complex API architectures, one of the most crucial aspects of building robust applications is mastering exception handling. When you implement custom middleware to manage routes, you often find yourself dealing with exceptions thrown from deep within the framework layers—like throttling mechanisms or Eloquent operations. The challenge, as you rightly pointed out, is how to gracefully catch these specific errors without breaking the core functionality or fighting against Laravel's built-in exception flow.

This post dives into the specifics of catching layered exceptions within a custom middleware in a Laravel environment, focusing on how to manage TooManyRequestsHttpException alongside application-specific errors like ModelNotFoundException.

The Anatomy of Layered Exceptions

Your experience perfectly illustrates the concept of exception stacking. When an error occurs during a request lifecycle, multiple layers can throw exceptions. For instance, if throttling intervenes, it throws a specific HTTP exception (TooManyRequestsHttpException). If that execution then fails because a model doesn't exist, another type of exception (ModelNotFoundException) might be generated.

Your initial setup successfully catches the application-specific errors:

catch( ModelNotFoundException $e ) {            
    return response()->json('not found', 404);
}
// ... and so on

However, you noted that the base throttle exception still takes precedence in some scenarios. This happens because the throttling middleware often throws an exception that sits higher up in the stack than your custom handler is designed to check first. To properly override this without modifying core files, we need a strategy focused on exception hierarchy and explicit control flow within the middleware.

Reframing the Middleware Strategy

The key to solving this lies not just in using try-catch, but in understanding where and how you handle the exceptions relative to each other. When handling multiple potential errors, prioritize the most specific ones first.

In your scenario, since throttling is a security/rate-limiting concern, it often needs to be handled distinctly from data access concerns (ModelNotFoundException).

Here is how we can refine your middleware to ensure clarity and proper response mapping:

namespace App\Http\Middleware;

use Closure;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException;
use Exception;

class ExceptionHandlerMiddleware
{
    public function handle($request, Closure $next)
    {
        try {
            $output = $next($request);
            return $output;
        }
        catch( TooManyRequestsHttpException $e ) {
            // Handle rate limiting specifically and return a 429 response
            return response()->json(['message' => 'Too many requests'], 429);
        }
        catch( ModelNotFoundException $e ) {            
            // Handle resource not found errors explicitly
            return response()->json(['message' => 'Resource not found'], 404);
        }
        catch( ValidationException $e ) {           
            // Handle validation failures
            return response()->json(['errors' => $e->errors()], 400);
        }
        catch( \Exception $e ) {            
            // Catch all remaining, unexpected errors (500)
            // Log the error here for debugging!
            \Log::error("Unhandled Exception: " . $e->getMessage(), ['trace' => $e->getTraceAsString()]);
            return response()->json(['message' => 'An unexpected error occurred'], 500);
        }
    }
}

Notice the shift in focus. By catching TooManyRequestsHttpException first, we isolate that specific flow. If an exception is not one of these known types, it falls into the final generic \Exception catch block. This pattern ensures that framework-level exceptions are handled with specific status codes (like 429) before falling back to a generic 500 error, providing much cleaner API responses.

Architectural Considerations for Laravel Applications

When building systems, especially those as scalable as the ones discussed in relation to Laravel Company, relying solely on custom middleware for all error mapping can become brittle over time. While your current approach works well for specific route protection, consider evolving this pattern:

  1. Service Layer Responsibility: Delegate complex data-fetching errors (like ModelNotFoundException) to the service or controller layer. Services should throw domain-specific exceptions, and controllers/middleware should map those custom exceptions to appropriate HTTP responses. This keeps your middleware focused purely on request flow management.
  2. Centralized Handler Refinement: For truly global error handling that spans the entire application (beyond just route middleware), leveraging Laravel's built-in app/Exceptions/Handler.php is powerful. While you want to avoid modifying core files, you can extend or modify this handler within your own service providers to register custom handlers for framework exceptions, ensuring consistency across all routes and controllers.

Conclusion

Successfully catching "too many attempts" exceptions in a Laravel middleware requires understanding the exception hierarchy and prioritizing specific error types over generic ones. By meticulously structuring your try-catch blocks—handling rate limits explicitly before falling back to general errors—you can create a highly resilient API layer. This defensive coding practice ensures that even when framework mechanisms throw unexpected errors, your application remains stable, predictable, and provides meaningful feedback to the client.