30

I am having trouble getting my validation message to be resolved.

I have been searching and reading through the web and SO for some hours now, I want to relate the question with the marked answer of Customize spring validation error

I do have a MessageSource bean defined and the messages.properties it getting read correctly, as I also use it for regular text to be displayed with th:text="#{some.prop.name}, which does work absolutely fine. It is just the validation error that won't work the way it should. I'm sure it's a stupid mistake I just overlook... The validation itself works fine.

Constraint:

@NotEmpty(message="{validation.mail.notEmpty}")
@Email()
private String mail;

messages.properties:

# Validation
validation.mail.notEmpty=The mail must not be empty!

Template part:

<span th:if="${#fields.hasErrors('mail')}" th:errors="*{mail}"></span>

The displayed text:

{validation.mail.notEmpty}

I tried a lot of variation, all without success.

@NotEmpty(message="validation.mail.notEmpty")
@NotEmpty(message="#{validation.mail.notEmpty}")

Will just show the exact value of the messages string, no parsing.

<span th:if="${#fields.hasErrors('mail')}" th:errors="${mail}"></span>
<span th:if="${#fields.hasErrors('mail')}" th:errors="#{mail}"></span>
<span th:if="${#fields.hasErrors('mail')}" th:errors="#{*{mail}}"></span>
<span th:if="${#fields.hasErrors('mail')}" th:errors="#{__*{mail}__}"></span>

Will result in an error.


EDIT:

After debugging, I stumbled up on this:

Class: org.springframework.context.support.MessageSourceSupport

Method: formatMessage(String msg, Object[] args, Locale locale)

will be called with

formatMessage("{validation.mail.notEmpty}", null, locale /*German Locale*/)

And it will run into if (messageFormat == INVALID_MESSAGE_FORMAT) {

So... my message format is not correct. This is way out of my scope/knowledge. Anyone knows what that means?

Szymon Stepniak
  • 40,216
  • 10
  • 104
  • 131
Korashen
  • 2,144
  • 2
  • 18
  • 28

8 Answers8

69

It looks like you are missing LocalValidatorFactoryBean definition in your application configuration. Below you can find an example of Application class that defines two beans: LocalValidatorFactoryBean and MessageSource that uses messages.properties file.

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;

@SpringBootApplication
public class Application {

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

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

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Having LocalValidatorFactoryBean bean defined you can use custom validation message like:

@NotEmpty(message = "{validation.mail.notEmpty}")
@Email
private String email;

and messages.properties:

validation.mail.notEmpty=E-mail cannot be empty!

and Thymeleaf template file with:

<p th:if="${#fields.hasErrors('email')}" th:errors="*{email}">Name Error</p>

Sample application

https://github.com/wololock/stackoverflow-answers/tree/master/45692179

I have prepared sample Spring Boot application that reflects your problem. Feel free to clone it and run it locally. It will display translated validation message if value posted with form does not meet @NotEmpty and @Email validation.

WebMvcConfigurerAdapter configuration

In case of extending WebMvcConfigurerAdapter you will have to provide validator by overriding getValidator() method from parent class, e.g.:

import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration
@EnableWebMvc
public class WebConfiguration extends WebMvcConfigurerAdapter {

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

    @Bean
    @Override
    public Validator getValidator() {
        LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
        bean.setValidationMessageSource(messageSource());
        return bean;
    }

    // other methods...
}

Otherwise if you define LocalValidatorFactoryBean bean in other place it will get overridden and there will be no effect.

I hope it helps.

Szymon Stepniak
  • 40,216
  • 10
  • 104
  • 131
  • I cloned your code and it works fine. Sure it does. But if I copy over the relevant part to my code (and adjust it), I get the same results... If you want, you can see my full code here: https://bitbucket.org/Korashen/theheroestavern (The Server will run on :80) – Korashen Aug 15 '17 at 12:54
  • @Korashen I've checked your code and updated my answer to cover a case where `WebMvcConfigurerAdapter` configuration class is used. – Szymon Stepniak Aug 15 '17 at 13:19
  • Holy Moly! That's it! It's working! I had to override the `getValidator()` method. 3 days of headaching finally coming to an end! Thank you so much! – Korashen Aug 15 '17 at 13:21
  • @Korashen Great, I'm glad I could help you :) Happy coding and good luck! – Szymon Stepniak Aug 15 '17 at 13:22
  • 1
    It is enough to define only a Validator bean (the second one). – Olga Sep 21 '17 at 13:58
  • 1
    Works fine on Spring Boot. – e-info128 Feb 25 '18 at 21:21
  • 1
    Not exactly what I was looking for, but it did the trick! Tested with Spring Boot 2.1.5 with REST api – Lukenzo Jul 14 '19 at 11:49
  • you don't need public MessageSource messageSource() { function if you put the messages directly under resources/messages.properties. So, remove it. Then to get MessageSource inside getValidator() function, just use setter injection. i.e. public Validator getValidator( MessageSource messageSource) – Junaed Sep 12 '20 at 08:04
24

Not sure which version of spring boot you are using. I am using Spring boot 2.0.1.RELEASE. A clearer solution would be move all your validation messages to ValidationMessages.properties. This way you don't have to override the auto-configured Validator() and setting the MessageSource.

want2learn
  • 2,471
  • 2
  • 20
  • 37
  • 1
    When I tested this with Spring Boot 2.1.3 with Thymeleaf, the locale of the browser was not taking into account for those messages in `ValidationMessages.properties`. See https://stackoverflow.com/a/55058274/40064 for how I solved it. – Wim Deblauwe Mar 08 '19 at 07:48
  • 2
    file ValidationMessages.properties should be placed directly in 'resources' directory, not in subdirectory, otherwise it will not work – Konstantin Ziubin Apr 02 '19 at 13:17
  • @want2learn, it really worked as per your reply but i think if we need to implement i18l then this will not work with your approach. – Sukh Jan 07 '20 at 16:23
  • You can simply follow i18n way for this properties as well. – want2learn Jan 09 '20 at 13:52
  • This solution works perfectly, tested with wiring in the validator `@Qualifier("defaultValidator") Validator validator` and using the `DataBinder` to validate the beans and do further processing. Apart from the `ValidationMessages.properties` , I added the `ValidationMessages_LC.properties` files with the language codes (like "en"). – loïc Feb 16 '21 at 09:47
5

I am using 2.2.7 Release of Spring boot and it worked by just changing the property file name to ValidationMessages.properties and no other config required.

Ramesh Singh
  • 73
  • 1
  • 9
3

I had the same issue and after reading the answers here I found out that the file name should only be 'ValidationMessages.properties'. I first named it something else and it wasn't working for me either. until I renamed it to ValidationMessages.properties

Wais Shuja
  • 113
  • 1
  • 9
3

regarding to the answer of Wais Shuja and Ramesh Singh I confirm with it because I think this is the proper solution you looking for. I expand their answer:

In folder /resources/ you can create as much files as languages you are support. In my web app I use German and English. Therefore, I create these two files:

 - ValidationMessages_en.properties
 - ValidationMessages_de.properties

Then, thanks to Spring magic, you can do this:

@NotNull(message = "{error.mandatory}")
@Size(min = 1, message = "{error.mandatory}")
private String forname;

Depending on the language stored in the CookieLocaleResolver Spring chooses appropriate file and inserts the text.

desertnaut
  • 57,590
  • 26
  • 140
  • 166
Kevin O.
  • 355
  • 3
  • 11
1

For rest controllers you will then have to add @Valid annotation on method parameter's request body. e.g

@PostMapping
public User create(@Valid @RequestBody User user){
  //...
}
A.Mushate
  • 395
  • 3
  • 7
0
@Configuration
public class LocalizationMessageSource {

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

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

And you should validation anotation with blank beyween message parameter like this ;

public class User{
  private @NotNull(message = "{serviceNumber.required}") String serviceNumber;

}

This usage is wrong one.

public class User{
  private @NotNull(message="{serviceNumber.required}") String serviceNumber;  
}
-3

after struggling with this problem ,i just reboot the computer and everything works fine i am using spring boot version 2.4.2