I'm building a spring boot application. I'm trying to use a decent way of handling rest responses when an exceptions is raised. So I extended the ResponseEntityExceptionHandler into a class named RestResponseEntityExceptionHandler.
The problem is that when an exception is thrown, the stackTrace is not printed in the console. when I delete this RestResponseEntityExceptionHandler class, the stacktrace is printed again in the console !
Here is what the RestResponseEntityExceptionHandler class looks like :
#RestControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = { IllegalArgumentException.class, TechnicalException.class})
protected void handleBadRequest(RuntimeException e, HttpServletResponse response) throws IOException
{
response.sendError(HttpStatus.BAD_REQUEST.value(), e.getMessage());
}
}
I am using logback for logging.
I found some tricks to deal with that, like adding a logger.error("Details : ", exception); which works fine and prints the stackTrace but I prefer not to use a solution like that since it works only for the exceptions handeled by that class... the other exceptions wont print the stackTrace.
Any explanations why the stackTrace is not printed ?
Thanx in advance.
Because You are handling Exception. If you want to print along with the handling, put logger inside ExceptionHandler methods.
It is not "printed" because you are already handling the exception in RestResponseEntityExceptionHandler.
Staketrace is not getting printed because you are handling the ResponseEntityExceptionHandler in the RestResponseEntityExceptionHandler by RestControllerAdvice and passing only the error message in response. It you want to print it specially then add a logger in your handleBadRequest method for the error i.e. e in your case.
Related
I have a component that implements the HandlerInterceptor interface, and implements the preHandle method. In this method I retrieve a parameter from the request, and throw an IllegalArgumentException if that parameter is missing.
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String parameter = request.getHeader("parameter123");
if (StringUtils.isEmpty(parameter)) {
throw new IllegalArgumentException("parameter123 not specified");
}
[...]
return true;
}
In another class annotated with #ControllerAdvice, I have a ExceptionHandler that catches the IllegalArgumentExceptions and turns those into a formatted response with HTTP status code 400.
When this is executed by triggering a valid path of my API, everything works just fine. Problems arise when I try to call an invalid/unexisting path of my API. The HandlerInterceptor is called and the exception is thrown but my ExceptionHandler is not triggered and the result is a basic HTTP status code 500 exception. It seems to both override the basic HTTP status 404 mechanism, while also preventing the triggering of my ExceptionHandlers (even an ExceptionHandler on Exception.class doesn't ever get called).
Any explanations regarding this behaviour are welcome ! Thanks
Although this may be an old question, I want to provide an answer for anyone who may come across it in the future.
When you raise an exception in the preHandle method of a HandlerInterceptor, it may be wrapped in another exception called NestedServletException. This is a specific exception thrown by the Spring framework.
It's worth noting that NestedServletException is a runtime exception that occurs when a servlet or filter throws an exception. It encloses the original exception and provides additional information about the location where the exception occurred.
I am doing some validation on a query parameter in my Spring Boot web service. In this case it is a parameter that does not match the regex [0-9]{3}. So in the service method, there is a validation:
#Pattern(regexp="[0-9]{3}") #Valid #RequestParam(value = "AngivelseFrekvensForholdUnderkontoArtKode", required = false) String angivelseFrekvensForholdUnderkontoArtKode
(angivelseFrekvensForholdUnderkontoArtKode is just the name of the query parameter)
I am working on a log manager that basically just prints log messages using logback and slf4j.
I have a writeInternalError(exception) in my log manager class which nicely logs an exception when told to:
public void writeInternalError(Exception exception) {
logger.error(exception.getClass().getName(), kv("LogType", exception), kv("LogMessage", exception));
}
except for when the ConstraintViolationException is caught by the #ExceptionHandler in my #ControllerAdvice. No errors are shown, and a Spring log is outputted instead of my expected custom log. When I debug, the logger.error() seems to be executed and no errors are shown.
I have made a quick fix method where I manually extract the information of the exception, but I want to use the same logging method for all exceptions:
public void writeTracelog(Exception exception) {
logger.error(exception.getClass().getName(), kv("LogType", "exception"), kv("ErrorMessage", exception.getMessage()), kv("StackTrace", exception.getStackTrace()));
}
The expected and unexpected logs I get are:
// The Spring log message shown instead of my custom error message:
{
"#timestamp": "2021-06-10T12:13:40.730+02:00",
"#version": "1",
"message": "Resolved [javax.validation.ConstraintViolationException: call29f0dab4A3094a30A1cdE29c01f28af8.angivelseFrekvensForholdUnderkontoArtKode: must match \"[0-9]{3}\"]",
"logger_name": "org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver",
"thread_name": "http-nio-8082-exec-1",
"level": "WARN",
"level_value": 30000
}
// How the log is supposed to look like
{
"#timestamp": "2021-06-10T14:35:18.257+02:00",
"#version": "1",
"message": "javax.validation.ConstraintViolationException",
"logger_name": "ClsLogManager",
"thread_name": "http-nio-8082-exec-1",
"level": "ERROR",
"level_value": 40000,
"LogType": "exception",
"LogMessage": {
"cause": null,
"stackTrace": [...],
"constraintViolations": null,
"message": "call29f0dab4A3094a30A1cdE29c01f28af8.angivelseFrekvensForholdUnderkontoArtKode: must match \"[0-9]",
"suppressed": [],
"localizedMessage": "call29f0dab4A3094a30A1cdE29c01f28af8.angivelseFrekvensForholdUnderkontoArtKode: must match \"[0-9]"
}
}
When I call writeInternalError() with any other exception, the log is nicely output. I have tried different ways of logging to see what works and what does not as you can see in the handler in the #ControllerAdvice
#ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(ConstraintViolationException.class)
protected ResponseEntity<Object> handleConflict(ConstraintViolationException ex, HttpServletRequest request) {
...
// Get the invalid parameter from the ConstraintViolationException
if (invalidParameter.equalsIgnoreCase("angivelseFrekvensForholdUnderkontoArtKode")) {
errorMessage = setErrorMessage(request, "422.9", HttpStatus.UNPROCESSABLE_ENTITY.value(), invalidValue);
clsLogManager.writeTracelog(ex); // Outputs customized unwanted log
clsLogManager.writeInternalError(new ConstraintViolationException(null)); // Outputs exception in the format I want
clsLogManager.writeInternalError(ex); // Outputs nothing
responseEntity = writeToAuditlog(request, inputHeaders, errorMessage); // Outputs an info log as it supposed to
return responseEntity; // Outputs the ExceptionHandlerExceptionResolver message after the return
}
// Do something else in case of another error
}
}
It looks like the logger cannot handle the exception, but why doesn't it tell me why, in case that is true, and why is the ExceptionHandlerExceptionResolver doing it instead?
Update:
I looked into ExceptionHandlerExceptionResolver as saver suggested, and found out that the log comes from AbstractHandlerExceptionResolver's logException(). My custom logger class' method gets called before logException(), but it still doesn't print anything. Can it be because it is a ConstraintViolationException that contains the field constraintViolations and that the logger does not know how to handle this?
There is a setWarnLogCategory method that I guess I can switch off if I don't want the Spring log. I just can't find out how. The javadocs for logException in AbstractHandlerExceptionResolver indicate that there is a property for this, but I don't know how to set it.
Update:
The issue is in method subAppend(E event) in OutputStreamAppender that class is parent of ConsoleAppender and in line of encoding:
byte[] byteArray = this.encoder.encode(event);
Encoder tried serialize ConstraintViolationException exception and jackson fails with error: HV000116: getParameterIndex() may only be invoked for nodes of type ElementKind.PARAMETER.
And as result of encoding is empty byte array in case of exception and this is reason why nothing is logged in console. See below:
I don't have a quick fix for that right now.
Old proposal:
I recommend to debug method doResolveHandlerMethodException in ExceptionHandlerExceptionResolver class and you can see there is only one place when spring boot logger can log message with warning level and that logger will work only in case when something happened during invoking handler method for exception (For example: incorrect type of parameter in handler method and so on). You will see the reason why your handler method wasn't called.
Please pay attention to case when class ConstraintViolationException can be located in two different packages:
javax.validation.ConstraintViolationException
org.hibernate.exception.ConstraintViolationException
of course in our case we should use ConstraintViolationException from javax.validation package
Those exceptions are handled in one of the base methods of ResponseEntityExceptionHandler. You need to override it.
Error stacktrace is not printed in console for the custom exception that is annotated with #ResponseStatus
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public class InternalErrorException extends RuntimeException {
public InternalErrorException(String message) {
super(message);
}
public InternalErrorException(String message, Throwable throwable) {
super(message, throwable);
}
}
Throwing exception like throw new InternalErrorException("error", e), never get the stacktrace printed in the console unlesss I remove the annotation #ResponseStatus
How could I get it printed while keeping the annotation #ResponseStatus?
See Annotation Type ResponseStatus API doc.
Warning: when using this annotation on an exception class, or when setting the reason attribute of this annotation, the HttpServletResponse.sendError method will be used.
With HttpServletResponse.sendError, the response is considered complete and should not be written to any further. Furthermore, the Servlet container will typically write an HTML error page therefore making the use of a reason unsuitable for REST APIs. For such cases it is preferable to use a ResponseEntity as a return type and avoid the use of #ResponseStatus altogether.
HttpServletResponse.sendError does not throw your error and I guess it is never logged because of that.
Maybe you want to implement exception handler for that exception to get it logged.
Related question
I create REST web-service with Spring Boot.
I would like to know what is a better way to handle exceptions in a controller. I have seen other questions and didn’t found an answer.
My controller:
#GetMapping
public ResponseEntity<?> saveMyUser(){
MyUser myUser = new MyUser(“Anna”);
//throws SQLException
MyUserDetails userDetails = userService.saveMyUser(myUser);
//if successful
return ResponseBody.ok(userDetails);
}
saveMyUser() method of UserService:
public MyUserDetails saveUser(MyUser) throws SQLException {...}
So at this point I have at least 2 simple options:
Add exception to method signature.
Here I may rely on Spring Boot to pass all information about exception and status code to a client. However do not know if it is a reliable approach.
Surround with try/catch and pass all information about exceptions manually.
What is a better simple way?
You can create an additional class with #ControllerAdivce annotation and later you will be able to write custom response logic for each exception e.g:
#ControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler({SQLException.class})
public ResponseEntity<Object> sqlError(Exception ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Some SQL exception occured");
}
}
Also, you can extend ResponseEntityExceptionHandler and override the default behavior for mapping from exceptions to HTTP response.
Also, take a look at this, it holds very usefull information for your case.
I have a method to handle a particular class of exceptions in a Spring MVC environment.
The metod (simplified) implementation follows
#ExceptionHandler(AjaxException.class)
#ResponseStatus(value=HttpStatus.BAD_REQUEST)
#ResponseBody
public Exception handleException(AjaxException ex) {
return ex;
}
This is works fine, but to return a different ResponseStatus I have to create a new handling method.
Is it possible to change the response status inside the method body instead of using the #ResponseStatus annotation without changing the return type?
If not, is it possible to achieve the same result changing the return type (maybe serializing the exception class by myself and returning it as a string)?
Add the HttpServletResponse to the method signature and simply call the setStatus method.
#ExceptionHandler(AjaxException.class)
#ResponseBody
public Exception handleException(AjaxException ex, HttpServletResponse response) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
return ex;
}
Something like that should work.
Easy done, reading a little more carefully the spring documentation.
It is possible to pass the HttpServletResponse as an object parameter. In such object is possible to set the return code. The syntax is as follows:
#ExceptionHandler(AjaxException.class)
#ResponseBody
public AjaxException handleException(AjaxException ex,HttpServletResponse response) {
//test code ahead, not part of the solution
//throw new NullPointerException();
//end of test code
response.setStatus(404);//example
return ex;
}
This will return the json serialization of the exception along with the specified http return code.
EDIT:
I deleted this answer yesterday because this solution didn't seem to work. The problem was a bit trickyer: when you manage an exception this way, if the method annotated with ExceptionHandler throws an exception itself then the thrown exception is ignored and the original exception is thrown instead.
My code was somehow like the solution I posted (it threw exception in the start of the method), so I couldn't see the json output, the standard spring exception handler was fired instead. To resolve I simply trycatched the exception-throwing line and everything was ok.