Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

JSR 303 Loading Messages from an I18N Property File

DZone's Guide to

JSR 303 Loading Messages from an I18N Property File

· Java Zone
Free Resource

Just released, a free O’Reilly book on Reactive Microsystems: The Evolution of Microservices at Scale. Brought to you in partnership with Lightbend.

This article will illustrate how to adapt the JSR 303 validation API to load messages from an I18n property file, and this by conserving all benefits of internationalisation and support for multiple languages.

To achieve this we are going to implement a custom MessageInterpolator which will be based upon Spring API for managing I18N messages.

Dependencies :

Below the required maven dependencies to make this work, the Javax validation and Hibernate validation are not listed in here :

<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.webflow</groupId>
<artifactId>spring-binding</artifactId>
<version>2.3.2.RELEASE</version>
</dependency>
</dependencies>

Configuration of MessageSource :

The first step is the configuration of the MessageSource bean which is responsible of scanning and indexing the content of properties files.

<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="defaultEncoding" value="UTF-8"/>
<property name="basenames">
<list>
<value>com.myproject.i18n.MyMessages</value>
<value>com.myproject.i18n.ErrorMessages</value>
</list>
</property>
</bean>

MyMessages and ErrorMessages are the properties files we wanted to scan, the name of the files support the conventions for multiple language.

For example if our application must support english and french then we should have : MyMessages_en.properties and MyMessages_fr.properties.

Custom MessageInterpolator :

In this custom MessageInterpolator we redefine the way JSR 303 resolve messages to display, we provide a custom implementation which uses Spring MessagesSource and the MessageBuild to search and prepare for the message to be displayed.

import java.util.Locale;
import javax.validation.MessageInterpolator;
import org.springframework.binding.message.MessageBuilder;
import org.springframework.context.MessageSource;
public class SpringMessageInterpolator implements MessageInterpolator {
@Autowired
private MessageSource messageSource,
@Override
public String interpolate(String messageTemplate, Context context) {
String[] params = (String[]) context.getConstraintDescriptor().getAttributes().get("params");
MessageBuilder builder = new MessageBuilder().code(messageTemplate);
if (params != null) {
for (String param : params) {
builder = builder.arg(param);
}
}
return builder.build().resolveMessage(messageSource, Locale.FRANCE).getText();
}
@Override
public String interpolate(String messageTemplate, Context context, Locale locale) {
String[] params = (String[]) context.getConstraintDescriptor().getAttributes().get("params");
MessageBuilder builder = new MessageBuilder().code(messageTemplate);
if (params != null) {
builder = builder.args(params);
}
return builder.build().resolveMessage(messageSource, local).getText();
}
}

Usage on a custom JSR 303 :

Let say that we create a new JSR 303 validation annotation, which validate will check if a field is not blank. To use the custom Spring message interpolator, we need to declare a message on one of the properties files loaded by the Spring Message source, lets declare that on the ErrorMessages.properties :

{com.myproject.validation.NotBlank}  Mandatory field

Best practice is to name the key of the message like the complete classe name of our validation annotation, you are free to choose any key name you want but it must be between the brackets {} to work.

Our custom annotation will look like below :

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = NotBlankValidator.class)
public @interface NotBlank {
String message() default "{com.myproject.validation.NotBlank";
Class<?>[] groups() default {};
String[] params() default {};
Class<? extends Payload>[] payload() default {};
}

Please verify that the default value of the message attribute is the same as the one you put on the property file.

Thats it, now you can use the annotation normally like you do, and if you don’t provide a hardcoded message it will get loaded from the property file if is declared there.

Strategies and techniques for building scalable and resilient microservices to refactor a monolithic application step-by-step, a free O'Reilly book. Brought to you in partnership with Lightbend.

Topics:

Published at DZone with permission of Mrabti Idriss, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}