Search code examples
javajpatransactionsquarkus

global Exception Handler overrules database transaction handler


I am sort of stuck at a particular funny error in a quarkus REST-service that I am currently developing. My application manages panache entities and I implemented some CRUD operations in a resource controller. I annotated that controller with @Transactional(like the documentation suggest) and for good measure I added a global exception handler which looks roughly like the following snippet:

import jakarta.ws.rs.core.Response;
import org.jboss.resteasy.reactive.server.ServerExceptionMapper;


public class MyExceptionHandler {
    @ServerExceptionMapper
    public Response entityToBig(EntityToBigException e) {
        return Response.status(Response.Status.BAD_REQUEST)
    // ErrorMessage is just a record to hold the message
    .entity(new ErrorMessage("Error", e.getMessage(), Response.Status.BAD_REQUEST.getStatusCode())).build();
    }

   //... other handlers
}

The thing is now, that if I throw a Exception which is handled by my ExceptionHandler class the Status code and message is presented correctly to the end-user but the changes done in the prior request are ALSO committed to the database!

If I throw a Exception that is not handled by my ExceptionHandler class the transaction rollback works just fine...

I could not find any other documentation on this issue so I am quite perplexed on how to solve this/manually tell the transaction manager to rollback my changes.


Solution

  • Ok so the fix is quite simple but very hidden away in the depths of the documentation. From https://quarkus.io/guides/transaction#starting-and-stopping-transactions-defining-your-boundaries we can learn that only RuntimeExceptions will automatically rollback the current transaction context.

    documentation telling us that only RuntimeException will be rolled back

    So that means if I, who of course tries to develop clean software, define a custom base Exception class for my service which is just a regular checked exception, that exception will NEVER rollback the current Transaction.

    The solution to this is pretty simple, take your class that is annotated with @Transactional (as the documentation suggest a REST-Controller) and add the prameter rollbackOn to the decorator.

    @ApplicationScoped
    @Path("/api/v1/")
    @Produces(MediaType.APPLICATION_JSON)
    @Tag(name = "CustomerNetwork API", description = "Operations related to customer networks")
    @Authenticated
    @Transactional(rollbackOn = {EntityToBigException.class})
    public class CustomerNetworkController {
     // REST endpoints ...
    }
    

    This ensures that the Transaction is rolled back whenever your custom exception or exceptions that inherit your exception are thrown.