Home » Java » java – Spring boot ExceptionHandler when an exception is thrown within a service-Exceptionshub

java – Spring boot ExceptionHandler when an exception is thrown within a service-Exceptionshub

Posted by: admin February 25, 2020 Leave a comment

Questions:

I am using ControllerAdvice to handle exceptions in my spring boot application.

@Order(Ordered.HIGHEST_PRECEDENCE)
@ControllerAdvice
public class ErrorApiHandler extends ResponseEntityExceptionHandler {

    final
    ResponsesHelper rh;

    public ErrorApiHandler(ResponsesHelper rh) {
        this.rh = rh;
    }

    @ExceptionHandler(UsernameNotFoundException.class)
    public ResponseEntity<Object> handleUsernameNotFoundException(UsernameNotFoundException ex) {
    log.error(ExceptionUtils.getStackTrace(ex));

    var error = buildError(ex);
    return rh.buildResponse(error, HttpStatus.NOT_FOUND);
    }
...
}

It works fine for exceptions thrown within my controllers.
However, with exceptions thrown, for example within a service the ControllerAdvice is not executed.

@Service
public class CustomUserDetailsService implements UserDetailsService {

    final
    UserRepository userRepository;

    public CustomUserDetailsService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    @Transactional
    public User loadUserByUsername(String email)
            throws UsernameNotFoundException {

        log.debug(String.format("Loading user %s", email));

        User user = userRepository.findByEmail(email)
                .orElseThrow(() -> {

                    log.debug(String.format("User %s not found", email));
                    return new UsernameNotFoundException("User not found : " + email); // <- This exception is not handled.
                });

        log.debug(String.format("User %s loaded", user));
        return user;
    }

How can I handle all exceptions thrown within my application?

Thanks in advance.

How to&Answers:

I found this in ResponseEntityExceptionHandler docs:

A convenient base class for @ControllerAdvice classes that wish to provide centralized exception handling across all @RequestMapping methods through @ExceptionHandler methods.

It seems that a custom exception handler that extends that class will only handle exceptions in the controller layer.

I found this tutorial – a solution that uses HandlerExceptionResolver sounds like the one you are looking for.

Answer:

@ControllerAdvice is meant to handle exceptions that propagate through controller methods (that are thrown from within controller method calls – including bubbling exceptions). So whenever you directly throw exceptions in your controller method or something that controller method is calling throws an exception, an attempt to handle it through advice will be made.

If you want your exceptions to be handled (somehow) outside of web context with a similar manner, your will have to write your own aspect that will literally wrap everything try-catch and will let you handle the exception.

Answer:

In my case my “CustomUserDetailsService” is called inside a filter:

public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Autowired
    private JwtTokenProvider tokenProvider;

    @Autowired
    private CustomUserDetailsService customUserDetailsService;

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain filterChain
        ) throws ServletException, IOException {

        try {
            String jwt = getJwtFromRequest(request);

            if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
                UUID userId = tokenProvider.getUserIdFromJWT(jwt);

                UserDetails userDetails = customUserDetailsService.loadUserById(userId);
...

I handled it by catching the exceptions there:

catch (UsernameNotFoundException ex){
            log.error(ExceptionUtils.getStackTrace(ex));

            var apiError = ErrorApiHandler.buildError(
                    new ResourceAlreadyTakenException(ex.getMessage())
            );

            response.addHeader("Content-Type", "application/json");
            response.setStatus(HttpStatus.BAD_REQUEST.value());
            PrintWriter writer = response.getWriter();
            writer.write(convertObjectToJson(apiError));
            writer.flush();
            return;
        }

Ideally, handle all exceptions with the controller advice, but this way it works