Many developers catch generic exceptions everywhere, which hides whether an error is minor or critical. Custom exceptions let you cleanly exit bad states and return structured responses without scattered if statements or early returns.
Here’s how many developers handle exceptions in controllers:
public function update(UpdateUserRequest $request)
{
try {
$user = $this->userService->activate($request->user_id);
return response()->noContent();
} catch (Exception $e) {
return response()->json(['error' => 'Something went wrong'], 500);
}
}
This catches everything—database errors, validation errors, network failures—and returns the same generic response. Debugging becomes a nightmare.
Create an exception that knows how to render itself:
namespace App\Exceptions;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class InvalidUserStatusException extends Exception
{
public function __construct(string $status)
{
parent::__construct("User status '{$status}' is invalid for this operation.");
}
public function render(Request $request): Response
{
return response()->json([
'error' => $this->getMessage()
], 422);
}
}
Your service throws the exception when something is wrong:
class UserService
{
public function activate(int $userId): User
{
$user = User::findOrFail($userId);
if ($user->status !== 'pending') {
throw new InvalidUserStatusException($user->status);
}
$user->status = 'active';
$user->save();
return $user;
}
}
Your controller stays clean:
public function update(UpdateUserRequest $request)
{
$this->userService->activate($request->user_id);
return response()->noContent();
}
No try-catch needed. Laravel catches the exception and calls its render() method automatically.
When you throw a custom exception, it ejects out of the current flow entirely. Laravel’s exception handler catches it and:
render() method if it existsreport() methodOther exceptions (database failures, etc.) still propagate normally to your error monitoring tools like Sentry.
This pattern keeps your controllers thin, centralises error responses, and makes debugging straightforward.