How to add info about the containing object when an hibernate validator fails in Quarkus

Julio Santana
2 min readNov 10, 2023

I use hibernate validators a lot on my API data transfer objects. They allow you on one hand to specify and document what your api expects and on the other to enforce that your API users send always valid data to it.

However, the API I am fortunate to work in, receives thousands of request per day, from many different types of devices and users. This makes the task of investigating errors hard up to the point when

id must not be blank

error message is not enough anymore. At some point I need more info about this object, what are the other attributes it has, and their values. This is why I decided to dig into the Quarkus framework until I found ResteasyReactiveEndPointValidationInterceptor, the one in charge of launching the validations and raising the exceptions with the found violations. It looks like this in Quarkus 3 code

@JaxrsEndPointValidated
@Interceptor
@Priority(Interceptor.Priority.PLATFORM_AFTER + 800)
public class ResteasyReactiveEndPointValidationInterceptor extends AbstractMethodValidationInterceptor {

@AroundInvoke
@Override
public Object validateMethodInvocation(InvocationContext ctx) throws Exception {
try {
return super.validateMethodInvocation(ctx);
} catch (ConstraintViolationException e) {
throw new ResteasyReactiveViolationException(e.getConstraintViolations());
}
}

@AroundConstruct
@Override
public void validateConstructorInvocation(InvocationContext ctx) throws Exception {
super.validateConstructorInvocation(ctx);
}
}

So it is a Java Interceptor that simply triggers the validation on the invoked API and if there is an exception return the Constraints that were violated on it. It also stop the interceptor flow here, meaning that there is no interceptor behind it.

So an easy way to overwrite this behaviour is to create our own Interceptor with a priority that is lower than 800 as the current one, so that it is executed first. It should do exactly the same as the previous (hence it will inherit from it), but enrich the error message with more details about the containing object, and it should also prevent the next one to be executed so that validation is not run twice.

@JaxrsEndPointValidated
@Interceptor
@Priority(Interceptor.Priority.PLATFORM_AFTER + 799)
public class LoggerResteasyReactiveEndPointValidationInterceptor
extends ResteasyReactiveEndPointValidationInterceptor {
private static final Logger logger =
LoggerFactory.getLogger(LoggerResteasyReactiveEndPointValidationInterceptor.class);

@AroundInvoke
@Override
public Object validateMethodInvocation(InvocationContext ctx) throws Exception {
try {
return super.validateMethodInvocation(ctx);
} catch (ConstraintViolationException e) {
e.getConstraintViolations()
.forEach(
current ->
logger.error(
"Constraint failing validation is: "
+ current.getMessage()
+ " on bean "
+ current.getLeafBean().toString()
+ " on field "
+ current.getPropertyPath()));
throw e;
}
}

And voila, we just hacked Quarkus one more time to make validators errors more meaningful.

--

--