I’m working on a Quarkus project using Hibernate Reactive and PostgreSQL. I have a method that persists a new user in the database, and I want to handle the case where a duplicate user (same email) causes a ConstraintViolationException due to the unique constraint on the email field.
I’ve set up an error handling block using .onFailure() to catch this exception and throw a custom UserAlreadyExistsException. However, the exception is not being caught in the failure block, and instead, I’m seeing an uncaught ConstraintViolationException in the logs.
@POST
@Path("/register")
@WithTransaction
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Uni<Response> registerUser(@Valid Auth authRequest) {
return authService.createUser(authRequest)
.onItem().transform(success -> Response.status(Response.Status.CREATED)
.entity(success).build())
.onFailure()
.recoverWithItem(ex -> {
log.error("Unable to create User, email: {}, Exception: {}", authRequest.getEmail(), ex.getMessage(), ex);
ErrorResponse errorResponse = new ErrorResponse(ex.getMessage(), Response.Status.INTERNAL_SERVER_ERROR);
return Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(errorResponse).build();
});
}
public Uni<Auth> createUser(Auth authRequest) {
User user = new User(authRequest);
String passwordHash = Password.hash(authRequest.getPassword()).withArgon2().getResult();
user.setPasswordHash(passwordHash);
return PanacheEntityBase.persist(user).onItem().transform(success -> {
log.info("Successfully created user, email: {}", authRequest.getEmail());
return authRequest;
})
.onFailure().invoke(ex -> {
log.error("Unable to create User, email: {}, Exception: {}",
authRequest.getEmail(), ex.getMessage(), ex);
throw new RegistrationFailedException(Errors.UNABLE_TO_REGISTER_USER + ex.getMessage());
});
}
What I Expect:
When trying to insert a user with an existing email (duplicate key), the ConstraintViolationException should be caught by the onFailure() block, and a custom UserAlreadyExistsException should be thrown.
What Happens:
Instead of being caught by the onFailure() block, the following error is returned:
org.hibernate.exception.ConstraintViolationException: error executing SQL statement
[ERROR: duplicate key value violates unique constraint "user_email_key" (23505)]
It seems like the failure handler isn’t catching the exception, and I get an uncaught exception response.
Calling .persist
tells Hibernate that the object needs to be saved in the database, but it doesn't tell when the insert query actually should occur.
In your use case, the query is executed when the transaction is committed.
Because you have added @WithTransaction
on the REST endpoint, it happens outside of the .recoverWithFailure
scope.
You have a couple of options:
.persist
, you can call .persistAndFlush
public Uni<Auth> createUser(Auth authRequest) {
User user = new User(authRequest);
String passwordHash = Password.hash(authRequest.getPassword()).withArgon2().getResult();
user.setPasswordHash(passwordHash);
return Panache
.withTransaction( () -> PanacheEntityBase.persist( user ) )
.onItem().transform( success -> {
log.info( "Successfully created user, email: {}", authRequest.getEmail() );
return authRequest;
} )
.onFailure().invoke( ex -> {
log.error( "Unable to create User, email: {}, Exception: {}", authRequest.getEmail(), ex.getMessage(), ex );
throw new RegistrationFailedException( Errors.UNABLE_TO_REGISTER_USER + ex.getMessage() );
} );
}
The second approach is usually the recommended one, because you should let Hibernate decide the best time to run a query.
By the way, I haven't tested it, but you could also remap the exception using @ServerExceptionMapper
: https://quarkus.io/guides/rest#exception-mapping