Close

Spring - Message codes created by MessageCodesResolver and their resolution to error messages

[Last Updated: Dec 22, 2023]

In Spring, when we call methods like Errors#rejectValue() (ValidationUtils methods), the underlying implementation of Errors will not only register the provided code, but also a number of additional codes. MessageCodesResolver (an interface) is responsible to build these message codes. By default DefaultMessageCodesResolver implementation is used. In following examples, we will understand the way these additional codes are created and will map them to error messages.

Examples

An Object to validate

package com.logicbig.example;

import java.math.BigDecimal;
import java.time.LocalDate;

public class Order {
  private LocalDate orderDate;
  private BigDecimal orderPrice;
    .............
}

Validator implementation

package com.logicbig.example;

import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import java.math.BigDecimal;

public class OrderValidator implements Validator {

  @Override
  public boolean supports(Class<?> clazz) {
      return Order.class == clazz;
  }

  @Override
  public void validate(Object target, Errors errors) {
      ValidationUtils.rejectIfEmpty(errors, "orderDate", "date.empty",
              "Date cannot be empty");
      ValidationUtils.rejectIfEmpty(errors, "orderPrice", "price.empty",
              "Price cannot be empty");
      Order order = (Order) target;
      if (order.getOrderPrice() != null &&
              order.getOrderPrice().compareTo(BigDecimal.ZERO) <= 0) {
          errors.rejectValue("orderPrice", "price.invalid", "Price must be greater than 0");
      }
  }
}

Performing validations

In following example, we are printing the all codes associated with each error object:

package com.logicbig.example;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.ValidationUtils;
import java.math.BigDecimal;
import java.util.Arrays;

@Configuration
public class SimpleValidationExample {

  @Bean
  OrderValidator validator() {
      return new OrderValidator();
  }

  public static void main(String[] args) {
      AnnotationConfigApplicationContext context =
              new AnnotationConfigApplicationContext(SimpleValidationExample.class);

      Order order = new Order();
      order.setOrderPrice(BigDecimal.ZERO);

      BeanPropertyBindingResult bindingResult =
              new BeanPropertyBindingResult(order, Order.class.getName());
      OrderValidator validator = context.getBean(OrderValidator.class);
      ValidationUtils.invokeValidator(validator, order, bindingResult);
      bindingResult.getAllErrors().forEach(e -> {
          Arrays.stream(e.getCodes()).forEach(System.out::println);
      });
  }
}

Output

date.empty.com.logicbig.example.Order.orderDate
date.empty.orderDate
date.empty.java.time.LocalDate
date.empty
price.invalid.com.logicbig.example.Order.orderPrice
price.invalid.orderPrice
price.invalid.java.math.BigDecimal
price.invalid

The additional codes are appended with a combination of fully qualified object/field names to our provided code (the ones we provided as the second argument of ValidationUtils.reject methods in our validator class). Check out doc for a detailed description of how different combination of these codes are generated.

We can map one of above codes (for each error) to the message strings in our resource file.

Customizing DefaultMessageCodesResolver

By default DefaultMessageCodesResolver uses Format.PREFIX_ERROR_CODE which appends the user provided code (for example, 'orderDate' in above code) at the beginning of the generated message codes. Let's change that to Format.POSTFIX_ERROR_CODE:

package com.logicbig.example;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.DefaultMessageCodesResolver;
import org.springframework.validation.ValidationUtils;
import java.math.BigDecimal;
import java.util.Arrays;

@Configuration
public class CustomizedCodesResolverExample {

  @Bean
  OrderValidator validator() {
      return new OrderValidator();
  }

  public static void main(String[] args) {
      AnnotationConfigApplicationContext context =
              new AnnotationConfigApplicationContext(CustomizedCodesResolverExample.class);

      Order order = new Order();
      order.setOrderPrice(BigDecimal.ZERO);
      BeanPropertyBindingResult bindingResult =
              new BeanPropertyBindingResult(order, Order.class.getName());
      DefaultMessageCodesResolver messageCodesResolver =
              (DefaultMessageCodesResolver) bindingResult.getMessageCodesResolver();
      messageCodesResolver.setMessageCodeFormatter(
              DefaultMessageCodesResolver.Format.POSTFIX_ERROR_CODE);
      OrderValidator validator = context.getBean(OrderValidator.class);
      ValidationUtils.invokeValidator(validator, order, bindingResult);
      bindingResult.getAllErrors().forEach(e -> {
          Arrays.stream(e.getCodes()).forEach(System.out::println);

      });
  }
}

Output

com.logicbig.example.Order.orderDate.date.empty
orderDate.date.empty
java.time.LocalDate.date.empty
date.empty
com.logicbig.example.Order.orderPrice.price.invalid
orderPrice.price.invalid
java.math.BigDecimal.price.invalid
price.invalid

Resolving codes to messages

Since all implementations of ApplicationContext implement ResourceLoader, we can use it to get resource messages:

package com.logicbig.example;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.DefaultMessageCodesResolver;
import org.springframework.validation.ValidationUtils;
import java.math.BigDecimal;
import java.util.Locale;

@Configuration
public class CodeToMessagesExample {

  @Bean
  OrderValidator validator() {
      return new OrderValidator();
  }

  public static void main(String[] args) {
      AnnotationConfigApplicationContext context =
              new AnnotationConfigApplicationContext(CodeToMessagesExample.class);

      Order order = new Order();
      order.setOrderPrice(BigDecimal.ZERO);
      BeanPropertyBindingResult bindingResult =
              new BeanPropertyBindingResult(order, Order.class.getName());
      DefaultMessageCodesResolver messageCodesResolver =
              (DefaultMessageCodesResolver) bindingResult.getMessageCodesResolver();
      messageCodesResolver.setMessageCodeFormatter(
              DefaultMessageCodesResolver.Format.POSTFIX_ERROR_CODE);
      OrderValidator validator = context.getBean(OrderValidator.class);
      ValidationUtils.invokeValidator(validator, order, bindingResult);
      bindingResult.getAllErrors()
                   .forEach(e -> System.out.println(context.getMessage(e, Locale.US)));
  }
}

Output

Date cannot be empty
Price must be greater than 0

In above example, since we did not provide any resource bundles, the default error messages (which we provided as last argument of ValidationUtils.reject methods) were printed.

Let's provide a properties file containing codes to message mappings:

src/main/resources/ValidationMessages_en.properties

orderDate.date.empty=Order Date must be non-empty.
orderPrice.price.empty=Order Price must be non-empty.
orderPrice.price.invalid=Order Price must be zero or less.
package com.logicbig.example;

import org.springframework.context.MessageSource;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.DefaultMessageCodesResolver;
import org.springframework.validation.ValidationUtils;
import java.math.BigDecimal;
import java.util.Comparator;
import java.util.Locale;

@Configuration
public class ResourceBundleMessageExample {

  @Bean
  OrderValidator validator() {
      return new OrderValidator();
  }

  @Bean
  public MessageSource messageSource() {
      ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
      messageSource.setBasename("ValidationMessages");
      return messageSource;
  }

  public static void main(String[] args) {
      AnnotationConfigApplicationContext context =
              new AnnotationConfigApplicationContext(ResourceBundleMessageExample.class);

      Order order = new Order();
      order.setOrderPrice(BigDecimal.ZERO);
      BeanPropertyBindingResult bindingResult =
              new BeanPropertyBindingResult(order, Order.class.getName());
      DefaultMessageCodesResolver messageCodesResolver =
              (DefaultMessageCodesResolver) bindingResult.getMessageCodesResolver();
      messageCodesResolver.setMessageCodeFormatter(
              DefaultMessageCodesResolver.Format.POSTFIX_ERROR_CODE);
      OrderValidator validator = context.getBean(OrderValidator.class);
      ValidationUtils.invokeValidator(validator, order, bindingResult);
      bindingResult.getAllErrors()
                   .stream()
                   .map(e -> context.getMessage(e, Locale.US))
                   .sorted(Comparator.naturalOrder())
                   .forEach(System.err::println);
  }
}

Output

Order Date must be non-empty.
Order Price must be zero or less.

Example Project

Dependencies and Technologies Used:

  • spring-context 6.1.2 (Spring Context)
     Version Compatibility: 3.2.3.RELEASE - 6.1.2Version List
    ×

    Version compatibilities of spring-context with this example:

    • 3.2.3.RELEASE
    • 3.2.4.RELEASE
    • 3.2.5.RELEASE
    • 3.2.6.RELEASE
    • 3.2.7.RELEASE
    • 3.2.8.RELEASE
    • 3.2.9.RELEASE
    • 3.2.10.RELEASE
    • 3.2.11.RELEASE
    • 3.2.12.RELEASE
    • 3.2.13.RELEASE
    • 3.2.14.RELEASE
    • 3.2.15.RELEASE
    • 3.2.16.RELEASE
    • 3.2.17.RELEASE
    • 3.2.18.RELEASE
    • 4.0.0.RELEASE
    • 4.0.1.RELEASE
    • 4.0.2.RELEASE
    • 4.0.3.RELEASE
    • 4.0.4.RELEASE
    • 4.0.5.RELEASE
    • 4.0.6.RELEASE
    • 4.0.7.RELEASE
    • 4.0.8.RELEASE
    • 4.0.9.RELEASE
    • 4.1.0.RELEASE
    • 4.1.1.RELEASE
    • 4.1.2.RELEASE
    • 4.1.3.RELEASE
    • 4.1.4.RELEASE
    • 4.1.5.RELEASE
    • 4.1.6.RELEASE
    • 4.1.7.RELEASE
    • 4.1.8.RELEASE
    • 4.1.9.RELEASE
    • 4.2.0.RELEASE
    • 4.2.1.RELEASE
    • 4.2.2.RELEASE
    • 4.2.3.RELEASE
    • 4.2.4.RELEASE
    • 4.2.5.RELEASE
    • 4.2.6.RELEASE
    • 4.2.7.RELEASE
    • 4.2.8.RELEASE
    • 4.2.9.RELEASE
    • 4.3.0.RELEASE
    • 4.3.1.RELEASE
    • 4.3.2.RELEASE
    • 4.3.3.RELEASE
    • 4.3.4.RELEASE
    • 4.3.5.RELEASE
    • 4.3.6.RELEASE
    • 4.3.7.RELEASE
    • 4.3.8.RELEASE
    • 4.3.9.RELEASE
    • 4.3.10.RELEASE
    • 4.3.11.RELEASE
    • 4.3.12.RELEASE
    • 4.3.13.RELEASE
    • 4.3.14.RELEASE
    • 4.3.15.RELEASE
    • 4.3.16.RELEASE
    • 4.3.17.RELEASE
    • 4.3.18.RELEASE
    • 4.3.19.RELEASE
    • 4.3.20.RELEASE
    • 4.3.21.RELEASE
    • 4.3.22.RELEASE
    • 4.3.23.RELEASE
    • 4.3.24.RELEASE
    • 4.3.25.RELEASE
    • 4.3.26.RELEASE
    • 4.3.27.RELEASE
    • 4.3.28.RELEASE
    • 4.3.29.RELEASE
    • 4.3.30.RELEASE
    • 5.0.0.RELEASE
    • 5.0.1.RELEASE
    • 5.0.2.RELEASE
    • 5.0.3.RELEASE
    • 5.0.4.RELEASE
    • 5.0.5.RELEASE
    • 5.0.6.RELEASE
    • 5.0.7.RELEASE
    • 5.0.8.RELEASE
    • 5.0.9.RELEASE
    • 5.0.10.RELEASE
    • 5.0.11.RELEASE
    • 5.0.12.RELEASE
    • 5.0.13.RELEASE
    • 5.0.14.RELEASE
    • 5.0.15.RELEASE
    • 5.0.16.RELEASE
    • 5.0.17.RELEASE
    • 5.0.18.RELEASE
    • 5.0.19.RELEASE
    • 5.0.20.RELEASE
    • 5.1.0.RELEASE
    • 5.1.1.RELEASE
    • 5.1.2.RELEASE
    • 5.1.3.RELEASE
    • 5.1.4.RELEASE
    • 5.1.5.RELEASE
    • 5.1.6.RELEASE
    • 5.1.7.RELEASE
    • 5.1.8.RELEASE
    • 5.1.9.RELEASE
    • 5.1.10.RELEASE
    • 5.1.11.RELEASE
    • 5.1.12.RELEASE
    • 5.1.13.RELEASE
    • 5.1.14.RELEASE
    • 5.1.15.RELEASE
    • 5.1.16.RELEASE
    • 5.1.17.RELEASE
    • 5.1.18.RELEASE
    • 5.1.19.RELEASE
    • 5.1.20.RELEASE
    • 5.2.0.RELEASE
    • 5.2.1.RELEASE
    • 5.2.2.RELEASE
    • 5.2.3.RELEASE
    • 5.2.4.RELEASE
    • 5.2.5.RELEASE
    • 5.2.6.RELEASE
    • 5.2.7.RELEASE
    • 5.2.8.RELEASE
    • 5.2.9.RELEASE
    • 5.2.10.RELEASE
    • 5.2.11.RELEASE
    • 5.2.12.RELEASE
    • 5.2.13.RELEASE
    • 5.2.14.RELEASE
    • 5.2.15.RELEASE
    • 5.2.16.RELEASE
    • 5.2.17.RELEASE
    • 5.2.18.RELEASE
    • 5.2.19.RELEASE
    • 5.2.20.RELEASE
    • 5.2.21.RELEASE
    • 5.2.22.RELEASE
    • 5.2.23.RELEASE
    • 5.2.24.RELEASE
    • 5.2.25.RELEASE
    • 5.3.0
    • 5.3.1
    • 5.3.2
    • 5.3.3
    • 5.3.4
    • 5.3.5
    • 5.3.6
    • 5.3.7
    • 5.3.8
    • 5.3.9
    • 5.3.10
    • 5.3.11
    • 5.3.12
    • 5.3.13
    • 5.3.14
    • 5.3.15
    • 5.3.16
    • 5.3.17
    • 5.3.18
    • 5.3.19
    • 5.3.20
    • 5.3.21
    • 5.3.22
    • 5.3.23
    • 5.3.24
    • 5.3.25
    • 5.3.26
    • 5.3.27
    • 5.3.28
    • 5.3.29
    • 5.3.30
    • 5.3.31
    • Compatible Java Version: 17+
    • 6.0.0
    • 6.0.1
    • 6.0.2
    • 6.0.3
    • 6.0.4
    • 6.0.5
    • 6.0.6
    • 6.0.7
    • 6.0.8
    • 6.0.9
    • 6.0.10
    • 6.0.11
    • 6.0.12
    • 6.0.13
    • 6.0.14
    • 6.0.15
    • 6.1.0
    • 6.1.1
    • 6.1.2

    Versions in green have been tested.

  • JDK 17
  • Maven 3.8.1

Resolving Codes To Validation Messages Select All Download
  • resolving-error-code
    • src
      • main
        • java
          • com
            • logicbig
              • example
                • SimpleValidationExample.java
          • resources

    See Also