javamicronautmicronaut-clientmicronaut-rest

Exception stacktrace and cause not mapped to the custom global exception handling in Micronaut 2.5.8


Trying to handle the global exception handling in Micronaut, but the exception stack trace and cause are not thrown to the ExceptionHandler.

public class GlobalException extends RuntimeException{
    public GlobalException(Throwable throwable){}
}



@Produces
@Singleton
@Requires(classes = {GlobalException.class, ExceptionHandler.class})
public class GlobalExceptionHandler implements ExceptionHandler<GlobalException, HttpResponse> {
    private static final Logger LOG = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @Override
    public HttpResponse handle(HttpRequest request, GlobalException exception) {
        LOG.error(exception.getLocalizedMessage());
        LOG.error(exception.getCause().getCause().getMessage());
        Arrays.stream(exception.getStackTrace()).forEach(item ->  LOG.error(item.toString()));
        return HttpResponse.serverError(exception.getLocalizedMessage());
    }
}

Controller

public Maybe<FindProductCommand> get(ProductSearchCriteriaCommand searchCriteria) {
        LOG.info("Controller --> Finding all the products");
        return iProductManager.find(searchCriteria)
                .onErrorResumeNext(error ->  { return Maybe.error(new GlobalException(error));});
    }

The actual error are not mapped in GlobalExceptionHandler. exception.getLocalizedMessage() is null and LOG.error(exception.getCause().getCause().getMessage()) is throwing null pointer exception


Solution

  • GlobalException's constructor has a Throwable parameter and it's swallowing it (not doing anything with it). RuntimeException also has a single argument constructor that takes a Throwable, so GlobalException(Throwable throwable) is effectively hiding RuntimeException(Throwable throwable).

    Thus when your controller gets to:

    return Maybe.error(new GlobalException(error));
    
    1. error is being swallowed
    2. exception.getLocalizedMessage() returns null since the RuntimeException(Throwable throwable) constructor can't propogate throwable to Exception(Throwable throwable) (RuntimeException extends Exception) and GlobalException doesn't override Exception#getLocalizedMessage()
    3. LOG.error(exception.getCause().getCause().getMessage()) is throwing a NullPointerException because exception.getCause() is returning null due to error being swallowed (from list item 1)

    In conclusion, either don't hide RuntimeException(Throwable throwable) in GlobalException, via:

    public class GlobalException extends RuntimeException {}
    

    Or just invoke RuntimeException(Throwable throwable) from GlobalException(Throwable throwable), via:

    public class GlobalException extends RuntimeException {
         public GlobalException(Throwable throwable) {
             super(throwable);
         }
    }