42

Is there a way in Spring Boot (MVC) to log a custom exception and throw it without its stack trace being visible in the log file? But for any other exception still see the stack trace.

Long explanation:

I am using spring boot to create a simple rest service. I like that for a custom exceptions there is no stack trace in the logs by default and JSON response is created with the basic exception details (status, error, message).

The problem is that it also creates no log entry at all, therefore I would have to do this manually:

Custom Exception

@ResponseStatus(value = HttpStatus.CONFLICT)
public class DuplicateFoundException extends RuntimeException {
    public DuplicateFoundException(String message) {
        super(message);
    }
}

Exception throwing in service method (in @RestController)

if (!voteDao.findByItemAndUser(item, voteDto.getUserId()).isEmpty()) {
    log.warn("... already voted ...");   //TODO: don't do this for every throw
    throw new DuplicateFoundException("... already voted ...");
}

Having more exceptions leads to placing log statement before each throw, which is a bad approach I think. I've tried removing all the log statements from the service method and created @ControllerAdvice where I would log all the custom exceptions and just re-throw them so I still get the nice JSON as before:

@ControllerAdvice
public class RestExceptionHandler {
    private static final Logger log = Logger.getLogger(RestExceptionHandler.class);

    @ExceptionHandler
    public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
        if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null) {
            log.warn(e.getMessage());
        } else {
            log.error("...");
        }
        throw e;
    }
}

Now the problem is that I see not just the log entry, but also the stack trace for custom exceptions and can't find a way how to prevent this. I think the problem is caused by throwing it again. A possible solution could be to create a custom class for exception which I will return instead, but I dislike the idea since exception marshalling seems to work fine.

Any hints?

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
rhorvath
  • 3,525
  • 2
  • 23
  • 30
  • So it's getting to the `log.warn` line but *also* logging the entire exception? I may be wrong but I've seen `@ExceptionHandler`s before that don't throw, they just return a `ResponseEntity` (with status 500, etc.) instead FWIW... – rogerdpack Jul 25 '18 at 17:38

6 Answers6

54

I'm using Spring Boot 2+ just add this line to your application.properties:

server.error.include-stacktrace=never

https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/autoconfigure/web/ErrorProperties.IncludeStacktrace.html

Carlos Negron
  • 904
  • 8
  • 6
  • As of 1.3.0, 'Never' is by default. Check the [release notes](https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-1.3.0-RC1-Release-Notes#mvc-stacktrace-output) – shark1608 May 12 '19 at 18:55
  • 1
    @shark1608 While it's true that the release notes say that, using Spring Boot 2.1.7 it was set to 'Always' by default – Robber Aug 13 '19 at 13:29
  • 2
    @Robber See my answer below, which may explain why it is getting set to ALWAYS – muttonUp Sep 29 '19 at 15:15
47

Be wary of Spring Boot DevTools.

Despite NEVER being the default for server.error.include-stacktrace, if you include Spring Boot DevTools it overides to ALWAYS.

If you're interested in more detail, see this commit, which became part of Spring Boot 2.1.0+ enter image description here

muttonUp
  • 6,351
  • 2
  • 42
  • 54
  • 1
    Thanks, appreciate the thoroughness. Always like to understand the 'why' part of a situation. Yet another case of unaware of the 'spring-boot magic'. – BitfulByte Dec 08 '19 at 15:09
  • If you update `application.yml` (or `application.properties`) to specify `NEVER` Spring Boot DevTools will respect it (at least in 2.3) – delitescere Jun 02 '20 at 06:40
  • um, check that you are not overriding DefaultErrorAttributes too and not calling getErrorAttributes with hardcoded includeStackTrace parameter - as I was doing, duh! – hello_earth Apr 02 '21 at 10:41
25

If you don't need the stack trace you can suppress the stack trace by overriding fillInStackTrace in your exception class.

public class DuplicateFoundException extends RuntimeException {
    @Override
    public synchronized Throwable fillInStackTrace() {
        return this;
    }
}

When you invoke e.printStackTrace() then no stack trace will be printed.

See also this blog post.

Roland Weisleder
  • 9,668
  • 7
  • 37
  • 59
  • thanks, stack trace is gone but I still see "2015-07-07 11:47:57.188 ERROR 41456 --- [nio-8080-exec-1] .m.m.a.ExceptionHandlerExceptionResolver : Failed to invoke @ExceptionHandler method: public org.springframework.web.servlet.ModelAndView". Is there a way to get rid of this as well so I don't see any error in log for custom exception ? I accepted the answer because the stack trace was gone, but now i see that my exception message is twice in the logs (1. log statement and 2. exception message from the failed method) – rhorvath Jul 07 '15 at 09:50
  • I think there is no easy way to do this. Maybe you should consider not using exceptions (i.e. a custom status code) as this case is not exceptional but expectable. http://stackoverflow.com/questions/3213094 – Roland Weisleder Jul 07 '15 at 11:34
  • my use case is basically the same as the example from official spring doc (https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc), therefore I would not call it expectable. Otherwise we could argue the point of the validationException as well. Creating a custom return object is possible as I also described in the question, but I was trying to find simpler and cleaner solution if possible. – rhorvath Jul 07 '15 at 12:35
  • 1
    @RolandWeisleder Thanks for your answer, it really helped me. – Ashu Phaugat Sep 19 '18 at 01:54
1

Call overloaded super constructor to avoid showing the stacktrace in response.

public class ProductCustomException extends Exception{
    
    private static final long serialVersionUID = -291211739734090347L;

    public ProductCustomException(String message) {
        super(message,null,false,false);
    }

}

Super loaded Constructor from Exception Class

 protected Exception(String message, Throwable cause,
                        boolean enableSuppression,
                        boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
    
}
C. Peck
  • 3,641
  • 3
  • 19
  • 36
Jeelan
  • 61
  • 4
0

Solution was to leave the exception handling to spring boot, so that custom exception is not logged and any other is logged by default. I removed the @ControllerAdvice and also the logging statements from the rest controller and added logging statement to custom exception constructor.

public DuplicateFoundException(String message) {
    super(message);
    LOGGER.warn(message);
}

I am not sure if this is the best approach, but now I have custom exception logging only at one place and don't have to repeat the log statement for every exception or see its stack trace or any other error message in the logs.

rhorvath
  • 3,525
  • 2
  • 23
  • 30
0

One other thing worth mentioning is that, for recent versions of Springboot like 3.0.4 in my case, when and if you build your application as a JAR file even if you have Devtools in your pom.xml and assuming that by default your server error stacktrace is set to never like this: server.error.include-stacktrace=never, your stacktrace will not show up since you use a custom exception class