9

In order to have unified exception handling throughout the application I am using Error Handling for REST with Spring solution#3 which uses @ControllerAdvice along with @ExceptionHandler.

Spring version: 4.3.22.RELEASE

Spring Boot version: 1.5.19.RELEASE

This is a Spring boot application and following is my package structure.

src/main/java
  com.test.app.controller
     MyRestController.java       -- This is my Rest controller
  com.test.app.handler
     RestExceptionHandler.java   -- This is my ControllerAdvice class

Following is my ControllerAdvice code and one of the Controller throws InvalidDataException but still the corresponding @ExceptionHandler is not called. Instead I am getting Unexpected 'e' as the response body with http 400.

@ControllerAdvice
public class RestExceptionHandler {

    @ExceptionHandler(InvalidDataException.class)
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    public @ResponseBody ErrorResponse handleValidationError(final InvalidDataException ex,
                                                             final WebRequest request) {
        log.error("InvalidDataException message:{} ", ex.getMessage());
        return getExceptionResponse("Failed with Invalid data" + ex.getMessage(), HttpStatus.BAD_REQUEST.value());
    }


    private ErrorResponse getExceptionResponse(final String message, final Integer errorCode) {
        final ErrorResponse exceptionResponse = new ErrorResponse();
        exceptionResponse.setErrorCode(errorCode.toString());
        exceptionResponse.setErrorDescription(message);
        log.error("message:{}", exceptionResponse);
        return exceptionResponse;
    }
}

I looked at other post on SO as well as other forums where they mentioned to use @EnableWebMvc and @ComponentScan etc. but nothing helped. Could someone please help me understand what am I missing?

Following is my Controller and corresponding interface.

@RestController
public class MyRestController implements MyApi {

    @Override
    public ResponseEntity<List<MyResponse>> myGet(@RequestHeader(value = "a") String a,
                                                               @RequestHeader(value = "b") String b,
                                                               @RequestHeader(value = "c") String c,
                                                               @RequestHeader(value = "d") String d,
                                                               @RequestHeader(value = "e") String e,
                                                               @RequestHeader(value = "f") String f,
                                                               @RequestHeader(value = "g") String g) {

      List<MyResponse> responses = service.getData(c, d, e, f); // This throws exception
      return new ResponseEntity<>(responses, HttpStatus.OK);
    }
}


@Validated
@Api(value = "My", description = "the My API")
//This is generated interface through swagger codegen
public interface MyApi {

    @ApiOperation(value = "", nickname = "myGet", notes = "", response = MyResponse.class, responseContainer = "List")
    @ApiResponses(value = {
        @ApiResponse(code = 200, message = "normal response", response = MyResponse.class, responseContainer = "List"),
        @ApiResponse(code = 400, message = "Request is invalid", response = ErrorResponse.class),
        @ApiResponse(code = 401, message = "", response = ErrorResponse.class),
        @ApiResponse(code = 404, message = "", response = ErrorResponse.class),
        @ApiResponse(code = 405, message = "", response = ErrorResponse.class),
        @ApiResponse(code = 409, message = "", response = ErrorResponse.class),
        @ApiResponse(code = 500, message = "Internal Server Error", response = ErrorResponse.class),
        @ApiResponse(code = 503, message = "Service Unavailable", response = ErrorResponse.class) })
    @RequestMapping(value = "/v1/test",
        produces = { "application/json" }, 
        method = RequestMethod.GET)
    default ResponseEntity<List<MyResponse>> myGet(@ApiParam(value = "a" ,required=true) @RequestHeader(value="a", required=true) String a,
                                                   @ApiParam(value = "b" ,required=true) @RequestHeader(value="b", required=true) String b,
                                                   @ApiParam(value = "c" ,required=true) @RequestHeader(value="c", required=true) String c,
                                                   @ApiParam(value = "d" ,required=true) @RequestHeader(value="d", required=true) String d,
                                                   @ApiParam(value = "e" ,required=true) @RequestHeader(value="e", required=true) String e,
                                                   @ApiParam(value = "f" ,required=true) @RequestHeader(value="f", required=true) String f,
                                                   @ApiParam(value = "g" ,required=true) @RequestHeader(value="g", required=true) String g) {
        getRequest().ifPresent(request -> {
            for (MediaType mediaType: MediaType.parseMediaTypes(request.getHeader("Accept"))) {
                if (mediaType.isCompatibleWith(MediaType.valueOf("application/json"))) {
                    ApiUtil.setExampleResponse(request, "application/json", "{  \"aNum\" : 0,  \"cNum\" : \"cNum\"}");
                    break;
                }
            }
        });
        return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
    }
}

Following is the code snippet from my GlobalExceptionHandler

class GlobalExceptionHandler extends ExceptionHandlerExceptionResolver implements HandlerExceptionResolver, Ordered, InitializingBean {
    ...
    @Override
    protected ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) {
        if (exception instanceof com.myframework.SystemException) {
            return new ServletInvocableHandlerMethod(this, exceptionMethods.get(com.myframework.SystemException.class.getName()));
        } else if (exception instanceof GenericApplicationException) {
            return new ServletInvocableHandlerMethod(this, exceptionMethods.get(com.myframework.GenericApplicationException.class.getName()));
        } else {
            return null;
        }
    }
    ....
}
fap
  • 663
  • 1
  • 5
  • 14
Learner
  • 1,503
  • 6
  • 23
  • 44
  • Show us the code of your MyRestController. – mentallurg Sep 03 '19 at 22:20
  • @mentallurg I have added the Controller code in the question, pls take a look. – Learner Sep 04 '19 at 13:19
  • 'Instead I am getting Unexpected 'e' as the' - do this appear in the console in a stack trace? If so, please, paste that stack trace in the question as well – Matheus Sep 05 '19 at 18:06
  • I get `Unexpected 'e'` in the response body with HTTP code 400. There is no exception trace in the Console – Learner Sep 05 '19 at 20:04
  • So what would you expect instead? Seems like your error handler is working fine. "Unexpected 'e' " is the exception message coming from your service, it is returned with http400 instead of http500 for unhandled exceptions. – tkruse Sep 07 '19 at 00:32
  • If you don't see an error log, maybe your logging configuration is wrong. – tkruse Sep 08 '19 at 02:22
  • also you should show the URL you use when you get the error, and possibly the parts of your main application config where you include both the controller and the ControllerAdvice – tkruse Sep 08 '19 at 02:24
  • what exception is code throwing and what does the stacktrace look like? – Kalpesh Soni Sep 09 '19 at 18:30
  • @KalpeshSoni I get Unexpected 'e' in the response body with HTTP code 400. There is no exception trace in the Console. – Learner Sep 09 '19 at 18:41

5 Answers5

9

It should work. Some possible causes that make it fails may be :

  1. The RestExceptionHandler is not declared as a spring bean yet ? @SpringBootApplication by default will only scan the spring beans to register under its package and all of its sub-packages.

  2. The controller is actually not throwing InvalidDataException but other exception ?

Anyway I suggest you to make the following changes to check if RestExceptionHandler is get called .

In your spring boot main application class , explicitly register RestExceptionHandler as a spring bean using @Import

@SpringBootApplication
@Import(RestExceptionHandler.class)
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Also in the RestExceptionHandler , also include a method to catch the most generic Exception :

@ControllerAdvice
public class RestExceptionHandler {

    @ExceptionHandler(Exception.class)
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    public ErrorResponse handleGenericException(final Exception ex ,final WebRequest request) {
        System.out.println("handleGenericException ....");
        return getExceptionResponse("Failed with Invalid data" + ex.getMessage(), HttpStatus.BAD_REQUEST.value());
    }
}

Please let me know if the RestExceptionHandler will get called after making these changes.

Ken Chan
  • 84,777
  • 26
  • 143
  • 172
  • Sure will try and let you know. – Learner Sep 09 '19 at 22:05
  • Hi @Ken, Thanks for your answer. As we are using an in-house framework I believe that is interfering with the error handling, and I see that following class from the framework gets called `class GlobalExceptionHandler extends ExceptionHandlerExceptionResolver implements HandlerExceptionResolver, Ordered, InitializingBean {}` and following method from that class gets called: `@Override protected ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) {..}` – Learner Sep 13 '19 at 15:00
  • Yes , I guess most probably it is interfering , for example your customised `GlobalExceptionHandler` may be configured to have higher priority to resolve he exception than the built-in one and it somehow cannot handle `@ControllerAdvice`.... – Ken Chan Sep 13 '19 at 15:51
  • I did give `@Order(Ordered.HIGHEST_PRECEDENCE)` on my RestExceptionHandler but even then it does not get called. How to have it call my handler? – Learner Sep 13 '19 at 16:24
  • Your customised GlobalExceptionHandler are overriding `getExceptionHandlerMethod()` which contain the logic of how to call the `@ControllerAdvice` bean. I am guessing are you override the logic about getting `@ControllerAdvice` ? As you do not show more codes about how to configure the exception handlers and what the `GlobalExceptionHandler` looks like , I can only guess ... – Ken Chan Sep 13 '19 at 17:10
  • I added the code snippet from my `GlobalExceptionHandler` in the question above, please let me know if more details are needed. – Learner Sep 13 '19 at 17:52
  • Did you get a chance to look at my above comment? – Learner Sep 17 '19 at 19:34
  • Not sure why you override `getExceptionHandlerMethod()` in `GlobalExceptionHandler` as you are override the logic to call @ControllerAdvice bean. Can you remove them to see if your @ControllerAdvice get called ? – Ken Chan Sep 17 '19 at 19:56
  • Or change the `return null` to `return super.getExceptionHandlerMethod( handlerMethod, exception) ` – Ken Chan Sep 17 '19 at 20:01
1

Make sure you have no other spring components that extend

AbstractErrorController
Kalpesh Soni
  • 6,879
  • 2
  • 56
  • 59
0

Below controller advice should solve the problem.

@ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {

 @ExceptionHandler(value = {InvalidDataException.class})
 protected ResponseEntity<Object> handleInvalidDataException(
  RuntimeException ex, WebRequest request) {
    return new ResponseEntity<>(getExceptionResponse("Failed with Invalid data" + ex.getMessage(), HttpStatus.BAD_REQUEST.value()), HttpStatus.BAD_REQUEST);
}
Suraj
  • 737
  • 6
  • 21
  • Here's a demo project of Global Error Handler in Spring Boot https://github.com/s2agrahari/global-excpetion-handler-spring-boot – Suraj Sep 09 '19 at 19:09
  • Don't think it needs to extend `ResponseEntityExceptionHandler` . I have use many `@ControllerAdvice` without extending `ResponseEntityExceptionHandler` in many projects and work very well.. – Ken Chan Sep 09 '19 at 19:16
  • Thanks @KenChan for reporting. Updated the answer – Suraj Sep 09 '19 at 19:25
0

replace @ControllerAdvice with @RestControllerAdvice

Andrea Ciccotta
  • 598
  • 6
  • 16
0

below code was enough for me to make the Adviser class workable in the Controller class

@SpringBootApplication
@Import(RestExceptionHandler.class)
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}