Can't get Hibernate Validator working with Spring MessageSource
Asked Answered
B

6

7

I'm trying to get Hibernate Validator setup to use messages from a Spring MessageSource. I have the following setup in my messages-context.xml:

<bean id="messageSource"
    class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
    <property name="basenames">
        <list>
            <value>WEB-INF/messages/messages</value>
            <value>WEB-INF/messages/exceptions</value>
            <value>WEB-INF/messages/fields</value>
            <value>WEB-INF/messages/buttons</value>
            <value>WEB-INF/messages/validation_errors</value>
        </list>
    </property>
</bean>

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

<bean id="localeChangeInterceptor"
    class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
    <property name="paramName" value="lang" />
</bean>

<bean id="localeResolver"
    class="org.springframework.web.servlet.i18n.CookieLocaleResolver">
    <property name="defaultLocale" value="en_GB" />
</bean>

I've tried a variety of approaches to how to pass the message key to the hibernate validator (both with and without enclosing {} and also without specificying a custom key - just using the default one:

@NotEmpty(message="validation.required.field")
@Length(max=255, message="{validation.too.long}")
private String firstName;

@NotEmpty(message="{validation.required.field}")
@Length(max=255, message="{validation.too.long}")
private String lastName;

@NotNull
@Past(message="{validation.must.be.past}")
private Date dateOfBirth;

@NotEmpty(message="{validation.required.field}")
@Length(max=255, message="{validation.too.long}")
private String email;

My validation_errors_en_GB.properties looks like this:

validation.required.field=this is a required field
validation.too.long=this field can only take {0} characters
validation.must.be.past=this date has to be in the past 
javax.validation.constraints.NotNull.message=Custom message

However, when the empty fields are validated, the messages displayed are this:

First name      validation.required.field
Last name       {validation.required.field}
Date of birth   may not be null
Email           {validation.required.field}

For whatever reason, the key of the message is always used - the actual message is never looked up. Any idea where I'm going wrong?

Thanks,

Russell

Bernabernadene answered 30/7, 2011 at 17:30 Comment(3)
I'm using: Spring 3.05, Hibernate-Validator 4.2.0.Final, Hibernate-Core 3.3.2.GA and Hibernate-Annotations 3.4.0.GABernabernadene
I tried using newer versions of hibernate-core and annotations, and I get a java.lang.IncompatibleClassChangeError: Implementing class when trying to create the sessionFactory, which suggests to me some sort of incompatibility between versions - but of what? Any ideas?Bernabernadene
Everyone mentions about how to configure spring to know about hibernate validator and message source, the need here is to let hibernate know to pick the springs message source instead of its own ValidationProperties fileDonate
D
13

This had me stumped for a while, but the problem is that you need to register with Spring the Validator used to validate @Controller methods (thanks to this answer for that insight!)

So if you are using XML config do something along these lines:

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

<mvc:annotation-driven validator="validator"/> 

And if you are using javaconfig, do something like this:

@EnableWebMVC
@Configuration
public MyWebAppContext extends WebMvcConfigurerAdapter {

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

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

(see Spring Web MVC framework documentation)

Dinnie answered 12/6, 2012 at 15:22 Comment(3)
Thank you for your answer - I've upvoted it rather than accepting it, as since posting this question I've moved away from Java so I no longer have the requisite setup to test it out. It "looks" like a correct answer though, so when I get the time to test it I will, and then accept it.Bernabernadene
Actually I might take that back - what's the difference between your suggestion and what's already in my messages-context.xml?Bernabernadene
Because you need to explicitly tell Spring which Validator bean to use for the MVC validation. Just having a Validator floating around in the Application Context isn't enough.Dinnie
D
4

It's because Hibernate validator is looking at another place for the error message resolver.

For the easiest thing to make it works, I think you can create a file name "ValidationMessages.properties" and put it in your classpath folder. Then put the error messages into that file (got from validation_errors_en_GB.properties)

By the way, the brackets are required when specifying error messages in model classes (message="{validation.too.long}")

Dewain answered 13/6, 2012 at 8:52 Comment(0)
S
2

Hibernate Validation is not aware of Spring's MessageSource. You will need to implement a MessageInterpolator. It may look something like below:

public class SpringMessageInterpolator implements MessageInterpolator {

    private final MessageResource messageResource;
    private final MessageInterpolator delegate;

    public SpringMessageInterpolator(MessageResource messageResource, MessageInterpolator delegate) {
        this.messageResource = messageResource;
        this.delegate = delegate;
    }

    @Override
    public String interpolate(String messageTemplate, Context context) {
        Locale locale = Locale.getDefault();
        return interpolate(messageTemplate, context, locale);
    }

    @Override
    public String interpolate(String messageTemplate, Context context, Locale locale) {
        try {
            Object[] args = {};
            return databaseMessageResource.getMessage(messageTemplate, args, locale);
        } catch (NoSuchMessageException ex) {
            return delegate.interpolate(messageTemplate, context, locale);
        }
    }

}
Sheedy answered 8/5, 2018 at 7:42 Comment(0)
R
1

Hibernate Validator is looking in a different place for the locale. Try this:

LocaleContextHolder.setLocale(locale);

Ramsdell answered 17/8, 2011 at 16:35 Comment(1)
Set it where? In the controller method? I gave that a go but it had no effect, sadly. Thanks for the suggestion though.Bernabernadene
V
0

This has worked for me.

<bean id="messageSource"
    class="org.springframework.context.support.ResourceBundleMessageSource">
    <property name="basename">
        <value>i18n/messages</value>
    </property>
</bean>

<bean id="validator"
    class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
    <property name="messageInterpolator">
        <bean
            class="org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator">
            <constructor-arg index="0">
                <bean
                    class="org.springframework.validation.beanvalidation.MessageSourceResourceBundleLocator">
                    <constructor-arg index="0" ref="messageSource" />
                </bean>
            </constructor-arg>
        </bean>
    </property>
</bean>

<bean
    class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor">
    <property name="validator" ref="validator" />
</bean>


<mvc:annotation-driven validator="validator" />
Valise answered 26/12, 2018 at 16:37 Comment(0)
G
0

By default Hibernate's validator (or better to say MessageInterpolator) checks ValidationMessages.properties and Spring checks Messages.properties for resolving validation messages.

In the realm of Spring you can configure these files as follows (suppose the messages are stored in the CustomValidationMessages.properties file and this file is located in the i18n directory):

@Configuration
public class ApplicationConfiguration {
    /*
     * Declaring the i18n messages location
     */
    @Bean
    public MessageSource messageSource() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        messageSource.setBasenames("i18n/CustomValidationMessages");
        messageSource.setDefaultEncoding("UTF-8");
        return messageSource;
    }

    /*
     * For Spring Validator: import org.springframework.validation.Validator;
     *
     * For Hibernate(JSR-303) Validator: import javax.validation.Validator;
     */
    @Bean
    public Validator validator() {
        LocalValidatorFactoryBean localValidatorFactoryBean = new LocalValidatorFactoryBean();
        localValidatorFactoryBean.setValidationMessageSource(messageSource());
        return localValidatorFactoryBean;
    }
}

Note: Pay attention to the imports that are mentioned in the comment section.

Category Entity (as example):

@Entity
public class Category {
    ...
    @NotEmpty(message = "{required.field.error}")
    private String name;
    ...
}

CustomValidationMessages.properties that holds validation messages:

required.field.error = This filed cannot be empty

Hibernate can give you messages in two different formats namely The interpolated and The non-interpolated error message. getMessage() gives you interpolated one (This filed cannot be empty) and getMessageTemplate() give you non-interpolated one ({required.field.error})

import javax.validation.Validator;
import javax.validation.ConstraintViolation;
..
@Service
public class CategoryService {

    @Autowired
    private Validator validator;

    private void insertNewCategory(Category category) {
        Set<ConstraintViolation<Category>> violations  = validator.validate(category);
        if (!violations.isEmpty()) {
            for (ConstraintViolation<Category> constraintViolation : violations) {
                System.out.println("interpolated: " +  constraintViolation.getMessage());
                System.out.println("non interpolated: " + constraintViolation.getMessageTemplate());
            }
        }
    }
    ....
}

output:

interpolated: This filed cannot be empty
non interpolated: {required.field.error}
Genna answered 21/8, 2021 at 17:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.