I am going to introduce the global handler on my web application:
#ControllerAdvice
public class GlobalControllerExceptionHandler {
#ExceptionHandler(CustomRuntimeException.class)
public #ResponseBody ImmutableMap<?, String> handleNullResponseException(CustomRuntimeException e) {
return ImmutableMap.of(e.getClass(), e.getMessage());
}
}
But the issue is that legacy code contains a few controllers with local handlers like this:
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
#ExceptionHandler(Exception.class)
public #ResponseBody ExceptionDetails handleException(Exception e) {
return handleException(e);
}
And when controller throws CustomRuntimeException it handles by local one not global. In order to fix it I can add to each of these controllers local handlers similar to global. But as for me it is not a good one.
The question: Is it possible to redirect handling custom exceptions to the global handler?
You need to put more specific Exceptions in Local ExceptionHandler and more general Exceptions in Global ExceptionHandler. Similar to Java Exception handling. If you put the General one in Local, all Exceptions will end up there because it is the closest one and accepts any exception.
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
#ExceptionHandler(IOException.class)
public #ResponseBody ExceptionDetails handleIOException(IOException e) {
return handleException(e);
}
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
#ExceptionHandler(NullPointerException.class)
public #ResponseBody ExceptionDetails handleNPException(NullPointerException e) {
return handleException(e);
}
Spring exceptionHandler works kind of like the try and catch
When the controller has an exception and it has a local exception handler the request will be handled by the local exception handler. Now if it does not find local then we tries to look for global.
In your case local exceptionHandler handles all exception thus global exception handler not called.
Related
I have my Spring error controller, and I need to get the actual exception class so I can print stack trace and other things like that.
This is my error controller
#Controller
public class ErrorController implements org.springframework.boot.web.servlet.error.ErrorController {
#RequestMapping("/error")
public String handleError() {
return "somethingwentwrong";
}
#Override
public String getErrorPath() {
return null;
}
}
I know its not much, but I need the exception object to be able to do some extra handling.
If you wish to execute different code based on the TYPE of the exception thrown, you should look at using #ControllerAdvice along with #ExceptionHandler. Any exceptions that you do not handle with an #ExceptionHandler will then bubble up to the default ErrorController (though you could handle Exception in a handler and then all exceptions will be handled via your custom handler). Something like:
#ControllerAdvice
#RestController
public class CustomResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(MyException.class)
protected ResponseEntity<String> handleMyException(MyException ex) {
// Your code here
}
#ExceptionHandler(Exception.class)
protected ResponseEntity<String> handleException(Exception ex){
// Your code here
}
}
https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-exceptionhandler
No worries guys, I solved the problem, I just had to do some digging into springs built in error controller to get the stacktrace.
By default, Spring Boot does not return messages for any exceptions, including ResponseStatusException, meaning that the message about bar below will not be returned to the client:
#GetMapping("/foo")
#ResponseBody
public Foo getFoo(#RequestParam(name = "bar", defaultValue = "0") int bar) {
if (bar <= 0) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "bar must always be positive");
}
return example.getFoo(bar);
}
This can be changed by setting server.error.include-message=always in the application.properties, however this causes ALL exception messages to be returned to the client, including this one:
#GetMapping("/baz")
#ResponseBody
public Baz getBaz() {
if (!security.checkSecurity()) {
throw new RuntimeException("Security breach! Hope no one finds out!");
}
return example.getBaz();
}
I know this is a trivial example and the solution would be just "don't throw server exceptions from your controller", but the exception might actually come from some other code buried deep in the application, it could even be a NullPointerException or whatever.
How can I get the application to show messages only from ResponseStatusException and not other types of exception? (I guess other than adding try-catch clauses to every single controller method.)
You can add extra (#ExceptionHandler) methods to any controller to specifically handle exceptions thrown by request handling (#RequestMapping) methods in the same controller. Such methods can:
Handle exceptions without the #ResponseStatus annotation (typically predefined exceptions that you didn’t write)
Redirect the user to a dedicated error view
Build a totally custom error response
Controller advice allows you to use exactly the same exception handling techniques but apply them across the whole application, not just to an individual controller. You can think of them as an annotation-driven interceptor.
Any class annotated with #ControllerAdvice becomes a controller-advice and three types of method are supported:
Exception handling methods annotated with #ExceptionHandler.
Model enhancement methods (for adding additional data to the model) annotated with #ModelAttribute. Note that these attributes are not available to the exception handling views.
Binder initialization methods (used for configuring form-handling) annotated with
#InitBinder.
Solution:
#ControllerAdvice
public class RestControllerAdvice {
#ExceptionHandler(ResponseStatusException.class)
public ResponseEntity<String> handleStatusException(ResponseStatusException exception) {
throw exception;
}
#ExceptionHandler(Exception.class)
public ResponseEntity<String> handleException(Exception exception) {
return new ResponseEntity<>("Exception", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Catch all the exceptions and in the catch block throw ResponseStatusException like in:
#GetMapping("/actor/{id}")
public String getActorName(#PathVariable("id") int id) {
try {
return actorService.getActor(id);
} catch (ActorNotFoundException ex) {
throw new ResponseStatusException(
HttpStatus.NOT_FOUND, "Actor Not Found", ex);
}
}
I have generic question about #ControllerAdvice and #ExceptionHandler. I have a rest controller annotated #RestController that has 2 apis. If argument validation fails, it throws MethodArgumentNotValidException. I created ExceptionHandler to handle this:
#ControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler(value = {MethodArgumentNotValidException.class})
public ResponseEntity<String> handleException(MethodArgumentNotValidException e) throws Exception {
return new ResponseEntity<>(e.getBindingResult().getGlobalError().getDefaultMessage(), HttpStatus.BAD_REQUEST);
}
}
If I want to log something when this exception happens, can I just add line of code before return statement like:
LOG.info("something happened");
Will it log it and then return BAD_REQUEST back to the caller?
If I want to log something when this exception happens, can I just add line of code before return statement like:
LOG.info("something happened");
Will it log it and then return BAD_REQUEST back to the caller?
Yes. That's the purpose of using #ExceptionHandlers. They help to reduce the code to handle exceptions across several rest endpoints defined in your project. This also serves as the single point to log exceptions, thus avoiding this anti pattern:
//BAD
class SomeService {
public SomeEntity someMethod() {
try {
/* stuff... */
} catch (Exception e) {
//No need to log the exception here
log.error("An exception happened", e);
throw e;
}
}
}
Still, you can have some benefits like wrapping the exceptions and rethrow them:
//GOOD
class SomeService {
public SomeEntity someMethod(String param) {
try {
/* stuff... */
} catch (Exception e) {
//You may use this to perform other logic like setting specific message or wrap your exception
log.error("Unexpected behaviour with param {}", param);
throw new MyCustomException("Some message", e);
}
}
}
You can think of the #ExceptionHandler as a giant catch block for all your rest endpoints and a specific type of exception.
Besides, your GlobalExceptionHandler class becomes the component with logic associated to handle every exception thrown in backend and handles how to report that to client side.
I'm writing a Spring Boot Application. And I'm implementing the exception handling right now.
I got the following problem.
My exception handlers look like this:
#ControllerAdvice
public class SpecialExceptionHandler {
#ExceptionHandler(MissingServletRequestParameterException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseObject missingServletErrorHandler(HttpServletRequest req, MissingServletRequestParameterException exception) {
//do something
return responseObject;
}}
And I got a general exception handler which looks
#ControllerAdvice
public class GeneralExceptionHandler {
#ExceptionHandler(Exception.class)
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ResponseObject defaultErrorHandler(HttpServletRequest req, Exception exception) {
// do something
return responseObject;
}}
But I got a problem: my application always runs into the GeneralExceptionHandler instead of the special handler unless I change the name of the GeneralExceptionHandler class to a name which comes alphabetically after the special exception handler (e.g. change 'GeneralExceptionHandler' to 'zGeneralExceptionHandler').
How can I resolve this issue?
You could try adding #Order(N) annotations on your ControllerAdvices, to force their registration order (where N is a int defining the order)
I have dao, service and action classes in my spring mvc application.
I am throwing Exception in Dao and Service classes. Now in Action, normally I have to write try catch block and in case exception occurs in dao and service, it will be thrown from there and it will go in catch block in action.
I have a error jsp which will be displayed.
Problem is I need to write same catch block in all action methods.
Is it possible to throw it again in action methods too and handle it from a single point rather than writing same code everywhere.
Please suggest.
You can also have a look at Spring Integration. It provides the use of gateways, filters and channels. Each can have a Request, Response and Error channel assigned. Or there is even a default error handler. In case all data flows through a specific channel, having a custom error handler is as simple as follows:
#MessageEndpoint
public class MyErrorHandler {
#ServiceActivator(inputChannel = "errorChannel")
public String handle(String messsage) {
// do whatever you like
}
}
The Integration framework offers lots of usefull stuff for general handling.
I think you are looking for cross-cutting exception handling and good news, you are working with Spring MVC yes you can use this feature.
All you need to do, is throw your CustomExcptions or whatever other Exceptions that are from your services to your action methods.
Let's say here is your service:
#Service
public class MyService {
public void someMethod throws RuntimeException {
...
}
}
In your controller method:
#Controller
public class MyController {
#Autowired
MyService service;
#RequestMapping("/someuri"){
try {
service.someMethod();
} catch {
throw new RuntimeException();
}
}
#ExceptionHandler(RuntimeException.class)
public ModelAndView handleException(RuntimeException ex) {
ModelAndView model = new ModelAndView("errorpage");
return model;
}
}
The handleException method annotated with ExceptionHandler is your advice method for exception handling and it will be called anytime a RuntimeException is throw inside your controller and you can keep up like this for all other exceptions.