Implementing something like Django's exception middleware in Play Framework

Originally published on svbtle on SEPTEMBER 13, 2016

One thing that I’ve found to reduce boilerplate is being able to throw exceptions from helper methods and have those be converted to responses. For instance, I often want to see if an object exists, and if not, return a 404.

This is easy (and obvious) in Django - I can just create a middleware class and override the exception handling method. In Play it's also easy, but it took a while to figure out where to put the logic. I think that I was looking for something as comprehensive as Django's middleware - something that handles requests, responses, and exceptions, but that doesn't exist in Play - those are all scattered around. To do something equivalent, you'd have to create a mixture of filters, actions, and creating your own ErrorHandler.

Looking at the exception handling alone, I find Django's system to be much nicer out of the box. In Django, there is a list of middleware classes that it runs through, executing like handle_exception(request, exception) for each one. Each middleware class can be really focused and do one thing. In Play, it seems like there is only one class that can be the ErrorHandler (you set it in application.conf, which I'm sure leads to really bloated error handler classes unless people take the time to architect it otherwise. On the other hand, I guess you could argue that it's weird for Django's middleware classes to be concerned with requests, responses, and exceptions. Oh well, it works and it's pretty simple for now, so I'm happy.

What didn't work

Here are some of the things I tried that didn't work:

  • Don’t use filters, because those seem like they’re only for incoming requests or modifying a response
  • Don’t use actions because the delegates don't throw exceptions, or they don't anymore. Not sure if that would have worked if they did anyways. What I was hoping for was something like:
public class ImmediateHttpResponseAction extends Action.Simple {

    @Override
    public CompletionStage<Result> call(Http.Context ctx) {

        try {
            return delegate.call(ctx);
        } catch (ImmediateHttpResponse e) {
            return CompletableFuture.supplyAsync(() -> status(e.status, e.message));
        }
    }
}
  • Next I toyed around with the heavier-handed approach of creating a base controller but then that would have involved a lot of work with the router and that would have been crazy.

What actually worked

The solution just up being to just override the error handler. Nice and simple. So, to do this:

  • Create your HttpResponse class:
package utils;


public class ImmediateHttpResponse extends Exception {

    public int status;
    public String message;

    public ImmediateHttpResponse(int status, String message) {
        this.status = status;
        this.message = message;
    }

    public ImmediateHttpResponse(int status) {
        this.status = status;
        this.message = "";
    }

}
  • Create a class in the app root called ErrorHandler and set play.http.errorHandler = null in application.conf.
public class ErrorHandler extends DefaultHttpErrorHandler {

    @Inject
    public ErrorHandler(Configuration configuration, Environment environment, OptionalSourceMapper sourceMapper, Provider<Router> routes) {
        super(configuration, environment, sourceMapper, routes);
    }

    @Override
    public CompletionStage<play.mvc.Result> onServerError(Http.RequestHeader request, Throwable exception) {
        if (exception.getClass() == ImmediateHttpResponse.class) {
            ImmediateHttpResponse ex = (ImmediateHttpResponse) exception;
            return CompletableFuture.supplyAsync(() -> Results.status(ex.status, ex.message));
        } else {
            return super.onServerError(request, exception);
        }
    }
}

And then use it in your controller:

public class FooController extends Controller {  
    private Foo getOr404(Long id) throws ImmediateHttpResponse {
        Foo foo = Foo.repository.byId(id);
        if (foo == null) {
            throw new ImmediateHttpResponse(404);
        }

        return contact;
    }

    public Result detail(Long id) throws Exception {
        Foo goo = getOr404(id);
        Form<Foo> form = formFactory.form(Foo.class);
        form = form.fill(foo);
        return ok(views.html.foo.detail.render(foo, form));
    }
}

And you're in!