4

I'm using Spring MVC with Spring Boot and Thymeleaf. I have normal controllers that return the name of a Thymeleaf template and REST controllers annotated with @RepsonseBody.

Let's say I have an EntityNotFoundException that is thrown by some code that is called by the controller. If it is thrown, I want to return a 404 status code and an error page or error message for REST controllers respectively.

My current setup for normal controllers:

@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
    public ResourceNotFoundException(String message) {
      super(message);
    }
}

@Controller
public class FooController {

  @RequestMapping("/foo") public String foo() {
    try {
       ...
    } catch (EntityNotFoundException enfe) {
      throw new ResourceNotFoundException(enfe.getMessage());
    }
    return "foo";
  }
}

For REST cotrollers I don't catch the exception and let a global exception handler pick it up:

@Controller
public class BarController {

  @RequestMapping("/bar")
  @ResponseBody
  public SomeDto bar() throws EntityNotFoundException {
    ...
    return someDto;
  }
}

@ControllerAdvice
public class ExceptionHandlerAdvice {

  @ExceptionHandler(EntityNotFoundException.class)
  public final ResponseEntity<Object> handleEntityNotFoundExceptionEntityNotFoundException enfe) {
    return new ResponseEntity<>(enfe.getMessage, HttpStatus.NOT_FOUND);
  }
}

I don't want to catch and rethrow in my normal controllers as well. The global handler should handle both:

  @ExceptionHandler(EntityNotFoundException.class)
  public final Object handleEntityNotFoundException(EntityNotFoundException enfe) {
    if (/* is REST controller? */) {
      return new ResponseEntity<>(enfe.getMessage(), HttpStatus.NOT_FOUND);
    } else {
      Map<String, Object> model = ImmutableMap.of("message", enfe.getMessage());
      return new ModelAndView("error", model, HttpStatus.NOT_FOUND);
    }
  }

Is there a way to determine where the exception came from, i.e. if the controller is annotated with @ResponseBody ore something alike?

3 Answers 3

6

I found a solution. You can inject the current controller method as a HandlerMethod parameter.

  @ExceptionHandler(EntityNotFoundException.class)
  public final Object handleEntityNotFoundException(EntityNotFoundException enfe, HandlerMethod handlerMethod) {

    boolean isRestController = handlerMethod.hasMethodAnnotation(ResponseBody.class)
        || handlerMethod.getBeanType().isAnnotationPresent(ResponseBody.class)
        || handlerMethod.getBeanType().isAnnotationPresent(RestController.class);

    if (isRestController) {
      return new ResponseEntity<>(enfe.getMessage(), HttpStatus.NOT_FOUND);
    } else {
      Map<String, Object> model = ImmutableMap.of("message", enfe.getMessage());
      return new ModelAndView("error", model, HttpStatus.NOT_FOUND);
    }
  }
Sign up to request clarification or add additional context in comments.

1 Comment

When I do that, I get a Failed to invoke @ExceptionHandler method: public java.lang.Object blabla.lExceptionHandler.handleException(java.lang.Exception,org.springframework.web.method.HandlerMethod) java.lang.IllegalStateException: No suitable resolver for argument [1] [type=org.springframework.web.method.HandlerMethod] Do I maybe need to tell Spring somewhere else to pass handlerMethod?
0

I would use separate Exceptions for normal controllers and and REST controllers, instead of doing some magic with special return codes or messages. This way you can write simple targeted exception handlers.

@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
    public ResourceNotFoundException(String message) {
      super(message);
    }
}

@ResponseStatus(HttpStatus.NOT_FOUND)
public class RestResourceNotFoundException extends RuntimeException {
    public ResourceNotFoundException(String message) {
      super(message);
    }
}

1 Comment

This is the exact opposite of what I want to achieve. This way, my REST controllers would need to re-throw the exception as well. This is not the case with my current solution, REST is already covered by the handler advice. I'd like to NOT throw ResourceNotFoundExceptions in normal controllers as well.
0

you can use error code as source reasone / point or create separeate exceptions for different exception source extended from ResourceNotFoundException .

with error codes you use one exception and work with different codes , with additionally exceptions you should create exrta classes.

public class ResourceNotFoundException extends RuntimeException {
    private Long sourceErrorCode;//if you use java8 use Optional

    public ResourceNotFoundException(String message) {
      super(message);
    }

    public ResourceNotFoundException(String message , Long sourceErrorCode) {
      super(message);
      this.sourceErrorCode = sourceErrorCode;
    }    
}

throw

 ......
catch (EntityNotFoundException enfe) {
  throw new ResourceNotFoundException(enfe.getMessage() , ERROR_SOURCE_CODE);
}

and Handler

 @ExceptionHandler(EntityNotFoundException.class)
  private Set<Long> REST_CODES = ..add... {code1 , code2...}

  public final Object handleSlugNotFoundException(EntityNotFoundException enfe) {
    //if you use java 8 use Optional
    if (REST_CODES.contains(enfe.getErrorCode())/* is REST controller? */) {
      return new ResponseEntity<>(enfe.getMessage(), HttpStatus.NOT_FOUND);
    } else {
      Map<String, Object> model = ImmutableMap.of("message", enfe.getMessage());
      return new ModelAndView("error", model, HttpStatus.NOT_FOUND);
    }
  }

and create class holder or enums for errors code to avoid code duplication

3 Comments

I want to get rid of ResourceNotFoundException. The point is to handle EntityNotFoundException directly instead of re-throwing it as frontend-specific exception.
you can use aspect for re-throwing and wrapping exception. in ControllerAdvice you don't know from where exception comes. it's like global per type exception try catch... but you have stacktrace , you can find source of exception from stacktrace , but rethrow with aspects better.
You try to avoid re throw in general as it's one extra exceptin or in source code?for source code use aspects for extra exceptin on work with stacktra e

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.