Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Boot @ControllerAdvice + @Transactional not working as expected

I'm working on a bets website so transactions are very important. I created an ExceptionHandler using @ControllerAdvice to catch all the exceptions from business layer.

@ControllerAdvice
public class HttpExceptionHandler {

  private String getStackTrace(final Throwable throwable) {
    final StringWriter stringWriter = new StringWriter()
    final PrintWriter printWriter = new PrintWriter(stringWriter, true)
    throwable.printStackTrace(printWriter)
    stringWriter.getBuffer().toString()
  }

  private List<String> filterStackTrace(final String stackTrace) {
    def stack = stackTrace.split('\n\t')
    stack.findAll({ it.contains('com.dsindigo.trading') })
  }

  private ResponseEntity<HttpErrorResponse> buildResponse(final Exception ex, final String message) {
    HttpErrorResponse error = new HttpErrorResponse(
      stack: filterStackTrace(getStackTrace(ex)),
      message: message
    )

    new ResponseEntity<HttpErrorResponse>(error, HttpStatus.BAD_REQUEST)
  }

  @ExceptionHandler(UsernameAlreadyExistsException)
  ResponseEntity<HttpErrorResponse> catchUsernameAlreadyExists(final UsernameAlreadyExistsException ex) {
    buildResponse(ex, HttpErrorMessages.USERNAME_ALREADY_EXISTS)
  }

  @ExceptionHandler(EmailAlreadyExistsException)
  ResponseEntity<HttpErrorResponse> catchEmailAlreadyExists(final EmailAlreadyExistsException ex) {
    buildResponse(ex, HttpErrorMessages.EMAIL_ALREADY_EXISTS)
  }

  //More exceptions...

  @ExceptionHandler(Exception)
  ResponseEntity<HttpErrorResponse> catchAny(final Exception ex) {
    buildResponse(ex, HttpErrorMessages.UNKNOWN)
  }
}

So basically it catches an exception (eg. UsernameAlreadyExistsException) and creates a JSON response containing a custom message and the stacktrace (for debugging purposes).

This is an example of a service throwing custom exceptions:

@Service
class UserServiceImpl implements UserService {

  // @Autowired stuff ...

  @Override
  @Transactional
  UserDTO save(UserDTO user) {
    UserDTO current = findOneByUsername(user.username)

    if (current != null)
      throw new UsernameAlreadyExistsException()

    current = findOneByEmail(user.email)

    if (current != null)
      throw new EmailAlreadyExistsException()

    ConfigurationDTO configuration = configurationService.findOne();

    user.active = false
    user.password = bCryptPasswordEncoder.encode(user.password)
    user.balance = configuration.initialBalance

    User entity = mapper.map(user, User)
    entity = userRepository.save(entity)
    user = mapper.map(entity, UserDTO)

    transactionService.createForUser(user.id, INITIAL_CHIPS_ID, configuration.initialBalance)

    TokenDTO token = tokenService.createForUser(user.id)
    emailService.sendRegisterMessage(user.email, token.token)

    user
  }
}

The problem is, when I throw a custom exception without @Transactional the exception handler executes the right method but adding @Transactional always executes the general Exception method.

Am I missing something?

like image 796
Jorge Ramón Avatar asked Dec 06 '25 03:12

Jorge Ramón


1 Answers

These declarations:

@ExceptionHandler(UsernameAlreadyExistsException)

Should be like this:

@ExceptionHandler(UsernameAlreadyExistsException.class)

Also, make sure that your exceptions are extending RuntimeException. If the exception is caught and handled anywhere else in your code (including Spring's interceptors!) it won't be handled by ControllerAdvice.

like image 177
Arlo Guthrie Avatar answered Dec 08 '25 17:12

Arlo Guthrie



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!