How to use a custom message from my .properties file, but using the parameters provided by the @Size annotation?

12k views Asked by At

That should be a simple problem but I couldn't find a straight forward solution.

I'm using the Spring Message Resource to provide all the texts on my system from a .properties file. I need that for internationalization questions and cannot use the default messages from the Spring.

Here is the problem:

I have the following line in my .properties file:

validator_invalid_state_name=Invalid name ({min}, {max})

The validation code is:

@Size( min=3, max=5, message="validator_invalid_state_name" )
public String name;

However, when an invalid value is sent to the field, the message is:

"Invalid name ({min}, {max})" and not "Invalid name (3, 5)".

I've tried several solutions without success, for example:

@Size( min=3, max=5, message="{validator_invalid_state_name}" )
@Size( min=3, max=5, message="${validator_invalid_state_name}" )
@Size( min=3, max=5, message="Size.name" ) // with "Size.name=Invalid name ({min}, {max})" in my .properties file
@Size( min=3, max=5, message="{Size.name}" ) //idem

But if I use only

@Size( min=3, max=5 )

Then, I get the default message from Spring (that I don't want to use). What I need is very simple: to use my own message from my custom .properties file, but using the parameters provided by the @Size annotation.

Any suggestions?

Thank you in advance.

2

There are 2 answers

1
Master Slave On

The proper syntax for accessing the key would be

@Size( min=3, max=5, message="{validator_invalid_state_name}" )

The problem however, is that in the default setup, the key lookup will not be done against your custom.properties, rather against Spring's ValidationMessages.properties. So the quickest way to get you up & running is to create a ValidationMessages.properties (and the 18n equivalents) and add your keys e.g.

validator_invalid_state_name=Invalid name ({min}, {max})

That's the simplest solution and no config headaches.

Now, since you would like this served from you properties file, well, its doable, but you'll have to find a proper configuration. For Spring 4.x, this would be the proper xml and java config.

Java Config

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

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

@Override
public Validator getValidator()
{
    return validator();
}

XML config

    <annotation-driven validator="validator">
    <!--i18n filtering-->
    <beans:bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
        <beans:property name="basenames">
            <beans:list>
                <beans:value>classpath:custom</beans:value>
            </beans:list>
        </beans:property>
        <beans:property name="defaultEncoding" value="UTF-8"/>
    </beans:bean>

  <beans:bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
        <beans:property name="validationMessageSource" ref="messageSource"/>
    </beans:bean>

For Spring mvc 3.x, you won't find the validationMessageSource property on the LocalValidatorFactoryBean, for that you will have to create you message interpolator, and set it up. You have an example in this thread http://forum.spring.io/forum/spring-projects/web/78675-jsr-303-validation-spring-mvc-and-messages-properties. Hope it helps

0
user497087 On

This solution does not work if you are using Spring Boot with JPA persistence support. The system throws;

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)#427e977': Cannot resolve reference to bean 'entityManagerFactory' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory': Post-processing of FactoryBean's singleton object failed; nested exception is java.lang.IllegalStateException: ApplicationEventMulticaster not initialized - call 'refresh' before multicasting events via the context: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@4f430ea5: startup date [Fri Sep 18 09:33:54 EEST 2015]; root of context hierarchy
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:359)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:108)
at org.springframework.beans.factory.support.ConstructorResolver.resolveConstructorArguments(ConstructorResolver.java:634)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:444)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1119)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1014)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:504)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveInnerBean(BeanDefinitionValueResolver.java:299)
... 96 more

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory': Post-processing of FactoryBean's singleton object failed; nested exception is java.lang.IllegalStateException: ApplicationEventMulticaster not initialized - call 'refresh' before multicasting events via the context: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@4f430ea5: startup date [Fri Sep 18 09:33:54 EEST 2015]; root of context hierarchy
at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.getObjectFromFactoryBean(FactoryBeanRegistrySupport.java:116)
at org.springframework.beans.factory.support.AbstractBeanFactory.getObjectForBeanInstance(AbstractBeanFactory.java:1523)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:314)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:351)
... 104 more
Caused by: java.lang.IllegalStateException: ApplicationEventMulticaster not initialized - call 'refresh' before multicasting events via the context: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@4f430ea5: startup date [Fri Sep 18 09:33:54 EEST 2015]; root of context hierarchy
at org.springframework.context.support.AbstractApplicationContext.getApplicationEventMulticaster(AbstractApplicationContext.java:344)
at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:331)
at org.springframework.boot.autoconfigure.orm.jpa.DataSourceInitializedPublisher.postProcessAfterInitialization(DataSourceInitializedPublisher.java:69)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:422)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.postProcessObjectFromFactoryBean(AbstractAutowireCapableBeanFactory.java:1719)
at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.getObjectFromFactoryBean(FactoryBeanRegistrySupport.java:113)
... 108 more

(What I think is happening is that somehow we loose sight of application.properties where the spring.datasource statements are located, but I have no idea why!). I managed to get this to work with the following java config, dropping the user definition of the messageSource as given by Master Slave above and injecting the SpringBoot provided messageSource. This now populates my validation messages from messages.properties instead of ValidationMessages.properties

@SpringBootApplication
public class MyApplication extends WebMvcConfigurerAdapter {

@Autowired
MessageSource messageSource;

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

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

@Override
public Validator getValidator()
{
    return validator();
}
}