Close

Java Bean Validation - Validation messages

[Last Updated: Aug 4, 2021]

Java Bean validation specifications require that each constraint annotation must include a 'message' element definition.

The message element value represents validation error message.

This element usage is optional on client side. If not specified a default message resource key is resolved and displayed.

Let's look at @NotNull annotation definition:

package javax.validation.constraints;

  .....
 public @interface NotNull {
	String message() default "{javax.validation.constraints.NotNull.message}";
    ...
 }

The values of default resource keys (javax.validation.constraints.NotNull.message in above example) are provided by hibernate (the Bean validation implementation).

Followings are the important things to know regarding messages:

  • When creating new constraint annotation, the message element must be included. We can specify a default resource key for the message element or we can put a hard-coded message text. Specifying a resource key is recommended
    In case of resource key, it must be within curly brackets {..}
  • Per conventions, the resource key should be fully qualified annotation class name appended with '.message', as seen in above @NotNull definition. That means messages should be the same through out the application for a given annotation and should not tie to a particular use on the client side.
  • Client can override the default messages on a particular annotation usage.
    @NotNull(message="name must not be null to be valid")
    String name;

    In above snippet, client can also use a new resource key instead. In that case we have to use curly brackets:

    @NotNull(message="{myBean.name.null.message}")
    String name;
    
  • If we want to override default messages with our resource keys or even the default keys, then the resource files must be in classpath and should be named as 'ValidationMessages_fr.properties' i.e. they should always start with 'ValidationMessages' then with underscore separated language code (locale). This is the default behavior which can be modified by implementing javax.validation.MessageInterpolator


Use of Expression language

Java Bean Validation specification supports Unified Expression Language (JSR 341) for evaluating dynamic expression in the validation messages that's the reason we have to include 'javax.el' dependency in our projects.

The value substitutions

Within the messages, constraint annotation element values can be referenced as '{theElementName}'.

For example consider @Min(5) or @Min(value=5)
Here we can use messages embedded with {value}, for example 'The min value must be {value}'.
Here {value} will be replace with 5 in case of validation error.

The expression can be evaluated for inline messages or in resource bundle files as well

Here are few examples from the Hibernate message resource implementation,
hibernate-validator-5.2.4.Final.jar!\org\hibernate\validator\ValidationMessages.properties:

javax.validation.constraints.DecimalMax.message  = must be less than ${inclusive == true ? 'or equal to ' : ''}{value}
javax.validation.constraints.DecimalMin.message  = must be greater than ${inclusive == true ? 'or equal to ' : ''}{value}
javax.validation.constraints.Digits.message      = numeric value out of bounds (<{integer} digits>.<{fraction} digits> expected)
javax.validation.constraints.Max.message         = must be less than or equal to {value}
javax.validation.constraints.Min.message         = must be greater than or equal to {value}
javax.validation.constraints.Pattern.message     = must match "{regexp}"
javax.validation.constraints.Size.message        = size must be between {min} and {max}

Using ${validatedValue}

This is the the currently validated value (property, bean, method parameter etc).

For example:

public class TestBean {

    @Size(min = 5, message ="The name '${validatedValue}' must be at least {min}" +
                        " characters long. Length found : ${validatedValue.length()}")
    private String name;

   // getters and setters
}

Now if we populate our bean like this and run the validations:

public static void main (String[] args) {
     TestBean testBean = new TestBean();
     testBean.setName("Mike");

     Validator validator = getValidator();
     validator.validate(testBean).stream().forEach(ValidatedValueExample::printError);
}

Output:

The name 'Mike' must be at least 5 characters long. Length found : 4

EL restrictions

Starting Hibernate Validator 6.2, a security risk was addressed. If user input is used for the bean values (the bean to be validated), arbitrary strings can be passed to the EL. These arbitrary strings might contain carefully crafted content to either expose sensitive data (a password hash present in a User bean for instance) or execute code. This is very similar to SQL injection vulnerability.

Starting 6.2 Hibernate introduced following level of EL restriction (via ExpressionLanguageFeatureLevel enum)

  • NONE

    Expression Language interpolation is fully disabled.

  • VARIABLES

    Allow interpolation of the variables injection only

  • BEAN_PROPERTIES

    Allow everything VARIABLES allows plus the interpolation of bean properties. (default)

  • BEAN_METHODS

    Allow everything BEAN_PROPERTIES allows plus the interpolation from execution of bean methods.

Changing the default level.

           ValidatorFactory validatorFactory = 
                              Validation.byProvider( HibernateValidator.class )   
                                        .configure()
                                        .customViolationExpressionLanguageFeatureLevel(
                                                                  ExpressionLanguageFeatureLevel.BEAN_METHODS)
                                        .buildValidatorFactory();

OR

        Configuration<?> config = Validation.byDefaultProvider().configure();
        ((ConfigurationImpl)config).constraintExpressionLanguageFeatureLevel(
                ExpressionLanguageFeatureLevel.BEAN_METHODS);

        ValidatorFactory factory = config.buildValidatorFactory();

Complete Examples

src/main/resources/ValidationMessages_en.properties

myBean.myString.null.msg = myString cannot be null
myBean.myString.pattern.msg = myString shouldn't contain numbers. regex={regexp}, value found=${validatedValue}
javax.validation.constraints.Future.message = Date must be in future. found: ${validatedValue}

Overriding default key

In this example we are going to override default resource key value of Future.

The Future definition snippet:

public @interface Future {
    String message() default "{javax.validation.constraints.Future.message}";
...
}

In above ValidationMessages_en.properties, we have already overridden the resource key as:

javax.validation.constraints.Future.message=Date must be in future. found: ${validatedValue}

Example code:

package com.logicbig.example;

import javax.validation.*;
import javax.validation.constraints.Future;
import java.util.Date;
import java.util.Locale;

public class OverrideDefaultKeyExample {

    private static class MyBean {
        @Future
        private Date date;

        public Date getDate() {
            return date;
        }

        public void setDate(Date date) {
            this.date = date;
        }
    }

    public static void main(String[] args) {
        MyBean myBean = new MyBean();
        myBean.setDate(new Date(System.currentTimeMillis() - 100000));

        Locale.setDefault(Locale.US);//Locale.FRANCE
        Configuration<?> config = Validation.byDefaultProvider().configure();
        ValidatorFactory factory = config.buildValidatorFactory();
        Validator validator = factory.getValidator();

        validator.validate(myBean).stream()
                 .forEach(OverrideDefaultKeyExample::printError);

        factory.close();
    }

    private static void printError(
            ConstraintViolation<MyBean> violation) {
        System.out.println(violation.getMessage());
    }
}

Output

Date must be in future. found: Wed Jul 28 03:00:29 CDT 2021

Providing different key

package com.logicbig.example;

import javax.validation.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import java.util.Locale;

public class ProvidingDifferentKeyExample {

    private static class MyBean {
        private String myString;

        @NotNull(message = "{myBean.myString.null.msg}")
        @Pattern(regexp = "\\D+", message = "{myBean.myString.pattern.msg}")
        public String getMyString() {
            return myString;
        }

        public void setMyString(String myString) {
            this.myString = myString;
        }
    }

    public static void main(String[] args) {
        MyBean myBean = new MyBean();
        //myBean.setMyString("jackie10");

        Locale.setDefault(Locale.US);//Locale.FRANCE
        Configuration<?> config = Validation.byDefaultProvider().configure();
        ValidatorFactory factory = config.buildValidatorFactory();
        Validator validator = factory.getValidator();
        validator.validate(myBean).stream().forEach(ProvidingDifferentKeyExample::printError);
        factory.close();
    }

    private static void printError(
            ConstraintViolation<MyBean> violation) {
        System.out.println(violation.getMessage());
    }
}

Output

myString cannot be null

Using Using ${validatedValue}

package com.logicbig.example;

import org.hibernate.validator.internal.engine.ConfigurationImpl;
import org.hibernate.validator.messageinterpolation.ExpressionLanguageFeatureLevel;
import javax.validation.*;
import javax.validation.constraints.Size;

public class ValidatedValueExample {

    private static class TestBean {

        @Size(min = 5, message = "The name '${validatedValue}' must be at least {min}" +
                            " characters long. Length found: '${validatedValue.length()}'")
        private String name;

        public String getName () {
            return name;
        }

        public void setName (String name) {
            this.name = name;
        }
    }

    public static void main (String[] args) {
        TestBean testBean = new TestBean();
        testBean.setName("Mike");

        Configuration<?> config = Validation.byDefaultProvider().configure();

        //6.2.0.Final
        ((ConfigurationImpl)config).constraintExpressionLanguageFeatureLevel(
                ExpressionLanguageFeatureLevel.BEAN_METHODS);

        ValidatorFactory factory = config.buildValidatorFactory();
        Validator validator = factory.getValidator();
        validator.validate(testBean).stream().forEach(ValidatedValueExample::printError);
        factory.close();
    }

    private static void printError (
                        ConstraintViolation<TestBean> violation) {

        System.out.println(violation.getMessage());
    }
}

Output

The name 'Mike' must be at least 5 characters long. Length found: '4'

Example Project

Dependencies and Technologies Used:

  • hibernate-validator 6.2.0.Final (Hibernate's Jakarta Bean Validation reference implementation)
     Version Compatibility: 5.0.0.Final - 6.2.0.Final Version List
    ×

    Version compatibilities of hibernate-validator with this example:

      groupId: org.hibernate
      artifactId: hibernate-validator
      Reference implementation for Bean Validation 1.1
    • 5.0.0.Final
    • 5.0.1.Final
    • 5.0.2.Final
    • 5.0.3.Final
    • 5.1.0.Final
    • 5.1.1.Final
    • 5.1.2.Final
    • 5.1.3.Final
    • 5.2.0.Final
    • 5.2.1.Final
    • 5.2.2.Final
    • 5.2.3.Final
    • 5.2.4.Final
    • 5.2.5.Final
    • 5.3.0.Final
    • 5.3.1.Final
    • 5.3.2.Final
    • 5.3.3.Final
    • 5.3.4.Final
    • 5.3.5.Final
    • 5.3.6.Final
    • 5.4.0.Final
    • 5.4.1.Final
    • 5.4.2.Final
    • 5.4.3.Final
    • groupId: org.hibernate.validator
      artifactId: hibernate-validator
      Reference implementation for Bean Validation 2.0
    • 6.0.0.Final
    • 6.0.1.Final
    • 6.0.2.Final
    • 6.0.3.Final
    • 6.0.4.Final
    • 6.0.5.Final
    • 6.0.6.Final
    • 6.0.7.Final
    • 6.0.8.Final
    • 6.0.9.Final
    • 6.0.10.Final
    • 6.0.11.Final
    • 6.0.12.Final
    • 6.0.13.Final
    • 6.0.14.Final
    • 6.0.15.Final
    • 6.0.16.Final
    • 6.0.17.Final
    • 6.0.18.Final
    • 6.0.19.Final
    • 6.0.20.Final
    • 6.0.21.Final
    • 6.0.22.Final
    • 6.1.0.Final
    • 6.1.1.Final
    • 6.1.2.Final
    • 6.1.3.Final
    • 6.1.4.Final
    • 6.1.5.Final
    • 6.1.6.Final
    • 6.1.7.Final
    • 6.2.0.Final
    • Version 7 and later:
      Jakarta Bean Validation 3.0
      jakarta.* packages

    Versions in green have been tested.

  • javax.el 3.0.0 (Expression Language 3.0)
  • JDK 1.8
  • Maven 3.8.1

Java Bean Validation - Error Messages Example Select All Download
  • validation-messages
    • src
      • main
        • java
          • com
            • logicbig
              • example
                • ProvidingDifferentKeyExample.java
          • resources

    See Also