jax-rsresteasybean-validationwildfly-swarm

Bean Validation with JAX-RS (rest-easy): parameter name not recognized


I'm using JAX-RS resources with Bean Validation and integration between these two works as expected.

However, the default error messages generated in case of a validation error report parameter names as arg0, like so

[PARAMETER]
[login.arg0.password]
[password is required]
[]

Corresponding method definition:

@POST //and other JAX-RS annotations
public Response login(
        @NotNull
        @Valid
        LoginBody loginBody) {

   [...]

protected static class LoginBody {

    @NotNull(message =  EMAIL_REQUIRED)
    public String email;

    @NotNull(message = PASSWORD_REQUIRED)
    public String password;
}

While I'm generally fine with this message pattern, what actually is annyoing, is the fact that the original parameter name is not recognized, i. e. I'd rather like to see

login.loginBody.password instead of arg0.

Is there an easy way to fix this, e. g. somehow provide an explicit name for that parameter?

I'm using WildFly Swarm 2017.6.0. From what I found out this means I have resteasy + resteasy-validator + hibernate-validator

Thanks.


Solution

  • You could try to compile your app with -parameters or instruct your IDE to do so, e.g. in case of eclipse: preferences -> java -> compiler -> "store information about method parameters (usable via reflection)"

    With that in place you then need to instruct the Bean Validation infrastructure (e.g. ) hibernate-validator to use the ReflectiveParameterNamer via META-INF/validation.xml.

    <parameter-name-provider>org.hibernate.validator.parameternameprovider.ReflectionParameterNameProvider</parameter-name-provider>
    

    See also Hibernate Validator Configuration

    I got something reliably working with the Paranamer library

    META-INF/validation.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <validation-config
       xmlns="http://jboss.org/xml/ns/javax/validation/configuration"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
                        http://jboss.org/xml/ns/javax/validation/configuration
                        validation-configuration-1.1.xsd"
       version="1.1">
       <default-provider>org.hibernate.validator.HibernateValidator
       </default-provider>
       <message-interpolator>org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator
       </message-interpolator>
       <traversable-resolver>org.hibernate.validator.internal.engine.resolver.DefaultTraversableResolver
       </traversable-resolver>
       <constraint-validator-factory>org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorFactoryImpl
       </constraint-validator-factory>
       <parameter-name-provider>org.hibernate.validator.parameternameprovider.ParanamerParameterNameProvider</parameter-name-provider>
    </validation-config>
    

    To get paranamer working with wildfly I needed to create a parameter-namer jboss-module and reference that module from the module.xml of the hibernate-validator module.

    With that in place I could simply write:

    @POST
    public Response login(@NotNull @Valid @Named("authRequest") AuthRequest authRequest) {
        return Response.ok().build();
    }
    ...
    
    public class AuthRequest {
    
        @NotNull(message = AuthMessages.EMAIL_REQUIRED)
        public String email;
    
        @NotNull(message = AuthMessages.PASSWORD_REQUIRED)
        public String password;
    }
    

    which yields the following response for a request sent via curl:

    curl -H "Content-Type: application/json" -H "Accept: application/json" -d '{"email":"foo@bar.com"}' -v http://localhost:8080/javaweb-training/resources/auth
    

    Response:

    {"exception":null,"fieldViolations":[],"propertyViolations":[],"classViolations":[],"parameterViolations":[{"constraintType":"PARAMETER","path":"login.authRequest.password","message":"password.required","value":""}],"returnValueViolations":[]}%
    

    ... note login.authRequest.password instead of just login.arg0.password