Custom Validation MessageSource in Spring Boot

Spring bean validation provides a powerful and flexible feature for data validation. In this article we will see how to configure custom validation MessageSource in Spring Boot.

 

Introduction

MessageSource is a powerful feature available in Spring applications. MessageSource is an interface that defines several methods for resolving messages. The ApplicationContext interface extends this interface so that all application contexts can resolve text messages. While working with validation API, we may want to display custom or user friendly messages to the customer. In this post, we will walk you through the steps to configure custom validation MessageSource in your application using Spring Boot.

 

1. Maven Configuration

We will create a Spring Boot web application in this post. Add the spring-boot-starter-web starter in your pom.xml to enable JSR 303 bean validation for your application. This is how our pom.xml look like:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

If you are working on a Spring Boot standalone application, you need to add the spring-boot-starter-validation in your pom.xml file:

 

2. Custom Validation Application

To see custom validation MessageSource in action, let’s create a simple example with following use cases:

  1. Customer need to register with our application.
  2. To register, they need to fill up a form, in case of missing or invalid values, we want to show helpful error messages to the customer

This is how our output will look like at the end of this post Custom Validation MessageSource in Spring Boot

2.1 Customer Input Form

To accept the customer information, we need to create customer input form. We will add validation constraints to this form. The bean validator will read These constraints and return error in case input data is not meeting these constraints. Let’s have a look at our bean class:

public class Customer implements Serializable {

    @NotEmpty(message = "{name.not.empty}")
    private String name;

    @Email(message = "{email.not.valid}")
    private String email;

    @Min(value = 21,message = "{age.adult.only}")
    private int age;

    //getter and setters
}

Let’s inspect few important points:

  1. We added a custom property (e.g. name.not.empty) the MessageSource configuration will resolve that.

 

3. The MessageSource Bean

Let’s define the MessageSource bean with detail about our resource bundle and encoding. You can define this bean in your application configuration class. We will use the ReloadableResourceBundleMessageSource that accesses resource bundles using specified base names:

@Bean
   public MessageSource messageSource() {
      ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
      messageSource.setBasename("classpath:messages");
      messageSource.setDefaultEncoding("UTF-8");
      return messageSource;
   }

 

4. LocalValidatorFactoryBean

To use our custom message, we need to register our MessageSource bean with the LocalValidatorFactoryBean. We will define this bean in the same configuration class:

@Bean
 public LocalValidatorFactoryBean validator(MessageSource messageSource) {
    LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
    bean.setValidationMessageSource(messageSource);
    return bean;
 }

 

5. Defining Property Files

The next step is to define the property files for our application. These properties file will contains the local specific message. Spring will automatically take care of referring the correct property file based on the locale. You need to create these files under src/resource directory. While creating these files, use the correct naming convention like messages_{locale}.properties.For this post, I will create two properties file (English and German).

This is how our properties file look like:

#English
name.not.empty=Please provide your name
email.not.valid=Plese provide valid email address
age.adult.only=You must be at least 21 for the registration
#German
name.not.empty=Bitte geben Sie Ihren Namen an
email.not.valid=Bitte geben Sie eine gültige E-Mail-Adresse an
age.adult.only=Sie müssen mindestens 21 für die Registrierung sein

I have used Google translator for this. Please correct me if something not translated correctly.

 

6. Locale Resolver and Locale Change Interceptor

LocalResolver is required to decide which local is currently used. We will use session based LocalResolver in our sample code. We need to configure an interceptor which allows for changing the current locale on every request, via a configurable request parameter.

@Bean
 public LocaleResolver localeResolver() {
    SessionLocaleResolver localeResolver = new SessionLocaleResolver();
    localeResolver.setDefaultLocale(Locale.US);
    return localeResolver;
 }

 @Bean
 public LocaleChangeInterceptor localeChangeInterceptor() {
    LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
    localeChangeInterceptor.setParamName("lang");
    return localeChangeInterceptor;
 }

 @Override
 public void addInterceptors(InterceptorRegistry registry) {
   registry.addInterceptor(localeChangeInterceptor());
 }

 

7. Sample Controller

Let’s create a simple controller to accept customer registration information and send it back in case there is any error reported by bean validation API. This is how our Spring MVC controller look like:

@Controller
public class CustomerController {

    @GetMapping("/customer")
    public String getCustomer(final Model model) {
        model.addAttribute("customer", new Customer());
        return "customer";
    }

    @PostMapping("/customer")
    public String createCustomer(@Valid Customer customer, BindingResult bindingResult,Model model){

        if(bindingResult.hasErrors()){
            return "customer";
        }
        model.addAttribute("msg", "Customer added");
        model.addAttribute("customer", customer);
        return "customer";
    }
}

 

8. Testing our Application

It’s time to see our application in action. You can either deploy your application or run your application using the main() method. Once we run our application, we will open the following link http://<<host>>:port/customer

customer registrationHit the submit button filling no value.Once you perform this action, from data will submitted to our controller. The @Valid annotation will help Spring MVC to kick in the underlying bean validation framework. Since we are not passing any value, the bean validation API will report validation error. This is how our form look like after validation:

validation errorThere are few important points to note:

  1. Bean validation picks up the English error messages. This happens because the default locale passed to the Spring is en (my case).
  2. If we pass different locale, Spring bean validation API will try to search messages on local specific messages property file.

To validate, let’s pass the language as DE in the request (http://localhost:8080/customer?lang=de). Since we have the message in German, Spring bean validation API should pick the messages accordingly.

localized messagesWith above steps, we have configure custom validation MessageSource in Spring Boot. Let’s look at some additional use cases which are very handy

9. Handle Multiple Files and Messages in Spring

Let’s say you want to have multiple files to handle messages, have different messages file for validation, business rules and say for the UI. A layout like:

resources
`-- messages
    |-- validation
    |   |-- validation.properties
    |   `-- validation_de.properties
    |-- business
    |   |-- business.properties
    |   `-- business_de.properties

To handle such use cases, we have following two options:

  1. Use the setBasenames method of ReloadableResourceBundleMessageSource.
  2. Create message resources per context

Let’s look at the code:

@Bean
public MessageSource messageSource() {
    ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
    messageSource.setBasenames("classpath:/messages/business/message", 
                               "classpath:/messages/validation/message");
    return messageSource;
}

Context base message resources:

@Bean
public MessageSource businessMessageSource() {
    ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
    messageSource.setBasename("classpath:/messages/business/message");
    return messageSource;
}

@Bean
public MessageSource validationMessageSource() {
    ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
    messageSource.setBasename("classpath:/messages/validation/message");
    return messageSource;
}

Spring Boot provides another mechanism to handle internationalization for your application. Read our article Internationalization in Spring Boot for more detail

 

Summary

In this article, we saw how to configure custom validation MessageSource in Spring Boot. We saw how to handle these messages through property files and how to use the Spring build in feature to handle localized messages.Source code for this article is available on GitHub.

Umesh

Hello!! I am Umesh- an engineer by profession and a photographer by passion.I like to build stuff on the web using OSS and love to capture the world through my lens.

follow me on:

Leave a Reply

avatar

This site uses Akismet to reduce spam. Learn how your comment data is processed.

  Subscribe  
Notify of