Why we stopped wrapping third-party SDK exceptions in HTTP controllers

Our controllers used to be full of boilerplate try/catch blocks catching vendor-specific SDK exceptions (Stripe, AWS S3, Twilio) and re-throwing custom domain errors or rendering HTTP responses.

// In a global PSR-15 ExceptionHandlingMiddleware:
$this->mapException(StripeExceptionCardException::class, function ($e) {
    return new JsonResponse([
        'error' => 'payment_failed',
        'message' => $e->getMessage()
    ], 422);
});

// Controllers stay completely clean of transport concerns:
public function charge(Order $order): Response
{
    $this->billingService->charge($order);
    return new JsonResponse(['status' => 'paid']);
}

Centralizing exception translation in middleware reduced controller verbosity by half and guaranteed that every endpoint formats error responses consistently across the entire API.