How can I override Dropwizard's default resource exception handling?
Asked Answered
K

5

8

Suppose I've got an endpoint in Dropwizard, say

@GET
public Response foo() { throw new NullPointerException(); }

When I hit this endpoint it logs the exception and everything, which is great! I love it. What I love less is that it returns a big status object to the user with status: ERROR (which is fine) as well as a gigantic stack trace, which I'm less excited about.

Obviously it's best to catch and deal with exceptions on my own, but from time to time they're going to slip through. Writing a try catch block around the entire resource every time is fine, but (a) it's cumbersome, and (b) I always prefer automated solutions to "you have to remember" solutions.

So what I would like is something that does the following:

  1. Logs the stack trace (I use slf4j but I assume it would work for whatever)
  2. Returns a general purpose error response, which does not expose potentially privileged information about my server!

I feel like there must be a built-in way to do this -- it already handles exceptions in a relatively nice way -- but searching the docs hasn't turned up anything. Is there a good solution for this?

Kinslow answered 16/12, 2016 at 21:55 Comment(2)
check this out: gary-rowe.com/agilestack/2012/10/23/…Centime
Would suggest to start differentiating the exception that you are throwing. Use custom exception for the failures you know and throw those with pretty logging. At the same runtime exception should be thrown and fixed. If you don't want to display that to the end user you can probably catch a generic exception, log the details and customize the Responseand entity accordingly.Marathon
K
5

As alluded to by reek in the comments, the answer is an ExceptionMapper. You'll need a class like this:

@Provider
public class RuntimeExceptionMapper implements ExceptionMapper<RuntimeException> {
    @Override
    public Response toResponse(RuntimeException runtime) {
        // ...
    }
}

You can do whatever logging or etc. you like in the toResponse method, and the return value is what is actually sent up to the requester. This way you have complete control, and should set up sane defaults -- remember this is for errors that slip through, not for errors you actually expect to see! This is also a good time to set up different behaviors depending on what kind of exceptions you're getting.

To actually make this do anything, simply insert the following line (or similar) in the run method of your main dropwizard application:

environment.jersey().register(new RuntimeExceptionMapper());

where environment is the Environment parameter to the Application's run method. Now when you have an uncaught RuntimeException somewhere, this will trigger, rather than whatever dropwizard was doing before.

NB: this is still not an excuse not to catch and deal with your exceptions carefully!

Kinslow answered 19/12, 2016 at 14:58 Comment(1)
This is interesting, but this doesn't cover exceptions thrown during configuration initialization, does it ? Cause you can set this only in the run method, right ? Personally, I would like Dropwizard to let exceptions thrown during configuration initialization kill the processLoram
I
4

Add the following to your yaml file. Note that it will remove all the default exception mappers that dropwizard adds.

server: registerDefaultExceptionMappers: false

Write a custom exception mapper as below:

public class CustomExceptionMapper implements ExceptionMapper<RuntimeException> {
    @Override
    public Response toResponse(RuntimeException runtime) {
        // ...
    }
}

Then register the exception mapper in jersey: environment.jersey().register(new CustomExceptionMapper());

Ify answered 11/1, 2017 at 12:19 Comment(1)
I tried doing that, but exceptions thrown during configuration initialization are still "caught" by dropwizard, and thus process does not shut down. Any idea on this ?Loram
M
0

Already mentioned this under the comments, but then thought I would give it a try with a use case.

Would suggest you to start differentiating the Exception that you would be throwing. Use custom exception for the failures you know and throw those with pretty logging. At the same RuntimeException should actually be fixed. Anyhow if you don't want to display stack trace to the end user you can probably catch a generic exception, log the details and customize the Response and entity accordingly.

You can define a

public class ErrorResponse { 

    private int code;
    private String message;

    public ErrorResponse() {
    }

    public ErrorResponse(int code, String message) {
        this.code = code;
        this.message = message;
    }
    ... setters and getters
}

and then within you resource code you can modify the method as -

@GET
public Response foo() { 
   try {
       ...
       return Response.status(HttpStatus.SC_OK).entity(response).build();
   } catch (CustomBadRequestException ce) {
       log.error(ce.printStackTrace());
       return Response.status(HttpStatus.SC_BAD_REQUEST).entity(new ErrorResponse(HttpStatus.SC_BAD_REQUEST, ce.getMessage())).build();
   } catch (Exception e) {
       log.error(e.printStackTrace(e));
       return Response.status(HttpStatus.SC_INTERNAL_SERVER_ERROR).entity(new ErrorResponse(HttpStatus.SC_INTERNAL_SERVER_ERROR, e.getMessage())).build();
   }      
}
Marathon answered 18/12, 2016 at 14:18 Comment(1)
This is perhaps good advice, but it doesn't really address the question, which is how to alter dropwizard's behavior when dealing with an uncaught exception. The implicit premise of the question, which you apparently disagree with, is that I don't want to surround each endpoint in a try-catch block when dropwizard essentially does that for me anyway.Kinslow
E
0

This article details Checked and Unchecked Exceptions implementation for Jersey with customized ExceptionMapper:

https://www.codepedia.org/ama/error-handling-in-rest-api-with-jersey/

Official Dropwizard documentation also covers a simpler approach, just catching using WebApplicationException:

@GET
@Path("/{collection}")
public Saying reduceCols(@PathParam("collection") String collection) {
    if (!collectionMap.containsKey(collection)) {
        final String msg = String.format("Collection %s does not exist", collection);
        throw new WebApplicationException(msg, Status.NOT_FOUND)
    }

    // ...
}

https://www.dropwizard.io/en/stable/manual/core.html#responses

Evvoia answered 3/2, 2020 at 14:30 Comment(0)
B
0

It worked for me by simply registering the custom exception mapper created in the run method of the main class.

environment.jersey().register(new CustomExceptionMapper());

where CustomExceptionMapper can implement ExceptionMapper class like this

public class CustomExceptionMapperimplements ExceptionMapper<Exception>
Binette answered 30/12, 2020 at 11:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.