Skip to content

Exception Filters

Exception filters are a powerful mechanism in Serinus that allow you to handle exceptions thrown during the request-response cycle. They provide a way to catch and process exceptions, enabling you to return custom error responses or perform specific actions when an error occurs.

HTTP ExceptionClient SideException FiltersRoute
dart
import 'package:serinus/serinus.dart';

class NotFoundExceptionFilter extends ExceptionFilter {
  NotFoundExceptionFilter() : super(catchTargets: [NotFoundException]);

  @override
  Future<void> onException(ExecutionContext context, Exception exception) async {
    if (exception is NotFoundException) {
      context.response.statusCode = 404;
      context.response.body = {'message': 'Resource not found'};
    }
  }
}

In the example above, we create a custom exception filter that catches NotFoundException and returns a 404 status code with a custom message.

Throwing standard exceptions

Serinus provides a built-in SerinusException class that you can use to handle errors in your application. This class is the base class for all exceptions in Serinus and provides a way to define custom exceptions with specific status codes and messages.

For example, in the UserController class, you can throw a SerinusException when the user is not found:

dart
import 'package:serinus/serinus.dart';

class UserController extends Controller {

  UserController() : super('/users') {
    on(Route.get('/<id>'), getUser);
  }
  
  Future<User> getUser(RequestContext context) async {
    final userId = context.params['id'];
    final user = await context.use<UserService>().getUserById(userId);
    if (user == null) {
      throw SerinusException('User not found', statusCode: 404);
    }
    return user;
  }

}

Built-in Exceptions

Serinus, by default, provides a set of built-in exceptions that you can use in your application. These exceptions are subclasses of SerinusException and are designed to handle common HTTP error scenarios. You can use these exceptions to simplify error handling in your application. These exceptions are automatically mapped to HTTP status codes, so you don't have to worry about manually setting the status code for each exception.

ExceptionDescriptionStatus Code
BadRequestExceptionThrown when the request is invalid.400
UnauthorizedExceptionThrown when the user is not authorized to access the resource.401
ForbiddenExceptionThrown when the user is not allowed to access the resource.403
NotFoundExceptionThrown when the requested resource is not found.404
MethodNotAllowedExceptionThrown when the method is not allowed on the resource.405
ConflictExceptionThrown when there is a conflict with the current state of the resource.409
GoneExceptionThrown when the requested resource is no longer available.410
PreconditionFailedExceptionThrown when the requested resource is no longer available.412
PayloadTooLargeExceptionThrown when the request payload is too large.413
UnsupportedMediaTypeExceptionThrown when the media type is not supported.415
UnprocessableEntityExceptionThrown when the request is valid, but the server cannot process it.422
TooManyRequestsExceptionThrown when the client has sent too many requests in a given amount of time.429
InternalServerErrorExceptionThrown when an internal server error occurs.500
NotImplementedExceptionThrown when the requested feature is not implemented.501
BadGatewayExceptionThrown when the gateway is bad.502
ServiceUnavailableExceptionThrown when the service is unavailable.503
GatewayTimeoutExceptionThrown when the gateway times out.504

All these exceptions have a message field that you can use to provide a custom error message.

dart
throw BadRequestException('Invalid request format');

Creating a Custom Exception

Let's say you need to add another field to the exception, such as a errors field. You can create a custom exception class that extends SerinusException and adds the new field:

dart
import 'package:serinus/serinus.dart';

class CustomException extends SerinusException {
  final List<String> errors;

  CustomException(String message, {required this.errors, int statusCode = 400}) 
    : super(message, statusCode: statusCode);
}

And as before, you can throw this exception in your controller:

dart
import 'package:serinus/serinus.dart';

class UserController extends Controller {

  UserController() : super('/users') {
    on(Route.get('/<id>'), getUser);
  }
  
  Future<User> getUser(Request request) async {
    final userId = request.params['id'];
    final user = await context.use<UserService>().getUserById(userId);
    if (user == null) {
      throw CustomException('User not found', errors: ['User with id $userId not found'], statusCode: 404);
    }
    return user;
  }

}

Binding Exception Filters

You can bind exception filters at different levels in your application: globally, at the controller level, or at the route level.

Global Exception Filters

You can bind an exception filter globally to your application using the use method on the SerinusApplication object.

dart
import 'package:serinus/serinus.dart';

Future<void> main() async {
  final app = await serinus.createApplication(
    entrypoint: AppModule()
  );

  // Add the exception filter to the application
  app.use(NotFoundExceptionFilter());

  // Start the application
  await app.serve();
}

Controller Exception Filters

You can also bind an exception filter to a specific controller using the exceptionFilters property of the Controller class.

dart
import 'package:serinus/serinus.dart';

class UserController extends Controller {

  List<ExceptionFilter> get exceptionFilters => [NotFoundExceptionFilter()];

  UserController() : super('/users') {
    on(Route.get('/<id>'), getUser);
  }

  Future<User> getUser(RequestContext context) async {
    final userId = context.params['id'];
    final user = await context.use<UserService>().getUserById(userId);
    if (user == null) {
      throw SerinusException('User not found', statusCode: 404);
    }
    return user;
  }
}

Route Exception Filters

You can also bind an exception filter to a specific route using the exceptionFilters property of the Route class.

dart
import 'package:serinus/serinus.dart';

class UserController extends Controller {

  UserController() : super('/users') {
    on(
      Route.get(
        '/<id>',
        exceptionFilters: {NotFoundExceptionFilter()}
      ), 
      getUser
    );
  }

  Future<User> getUser(RequestContext context) async {
    final userId = context.params['id'];
    final user = await context.use<UserService>().getUserById(userId);
    if (user == null) {
      throw SerinusException('User not found', statusCode: 404);
    }
    return user;
  }
}

© 2025 Francesco Vallone. Built with 💙 and Dart 🎯 | One of the 🐤 of Avesbox.