How can I i18n a spring #RestController property on javax.validation constraints?
I thought I could just add /src/main/resources/messages.properties and messages_de.properties, and then spring would detect them and enable proper i18n? But that seems not to be the case:
#RestController
public void TestController {
#PostMapping
public void post(#Valid #RequestBody Person p) {
}
}
public class Person {
private String firstname;
#javax.validation.constraints.NotBlank(message = "{errors.person.lastname}")
private String lastname;
}
messages.properties:
errors.person.lastname=person should provide a lastname
messages_de.properties:
errors.person.lastname=Person ohne Nachnamen
Problem: if I now send a POST request:
{
"exception": "org.springframework.web.bind.MethodArgumentNotValidException",
"message": "'lastname': {errors.person.lastname}"
}
Question 1: do I really have to tell spring explicit to apply i18n as follows? Or can I somehow rely on auto-detect features?
Next step was adding the following configuration. Now the default message is resolved properly. But is it necessary to really add this whenever I want to have validation i18n?
#Configuration
public class MessageSourceConfig {
#Bean
public LocalValidatorFactoryBean localValidatorFactoryBean() {
LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
bean.setValidationMessageSource(messageSource());
return bean;
}
#Bean
public MessageSource messageSource() {
ResourceBundleMessageSource bundle = new ResourceBundleMessageSource();
bundle.setBasenames("messages");
return bundle;
}
}
Question 2: I those beans are required: how can I now send a post request to switch the language?
I tried adding get-query parameters like ?local=de, locale=de, ?lang=de, language=de, but none of them worked...
With the help of #M. Deinum above, this is the missing peace:
#Bean
public LocalValidatorFactoryBean localValidatorFactoryBean(MessageSource messageSource) {
LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
bean.setValidationMessageSource(messageSource);
return bean;
}
Then having to send a web request with http header Accept-Language=de.
Related
I followed some tutorials for internationalization in Spring Boot.
(https://www.youtube.com/watch?v=Y7pHhui0cD4 and https://www.baeldung.com/spring-boot-internationalization)
I came so far to configure it like this:
#Configuration
public class InternationalizationConfiguration {
#Bean
public LocaleResolver localeResolver() {
SessionLocaleResolver sessionLocaleResolver = new SessionLocaleResolver();
sessionLocaleResolver.setDefaultLocale(Locale.GERMAN);
return sessionLocaleResolver;
}
#Bean
public ResourceBundleMessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("messages");
messageSource.setUseCodeAsDefaultMessage(true);
return messageSource;
}
}
and to use it in the RestConroller like this:
#RestController
public class ExperimentalControllerImpl implements ExperimentalController{
#Autowired
ResourceBundleMessageSource messageSource;
#Override
public String testI18n(#RequestHeader("Accept-Language") String locale) {
StringBuilder builder = new StringBuilder();
builder.append(messageSource.getMessage(Messages.HELLO, null, new Locale(locale)));
builder.append("\n");
builder.append(messageSource.getMessage(Messages.HOW_ARE_YOU, null, new Locale(locale)));
return builder.toString();
}
}
It works fine.....
But how do I get the locale information from the request header to my service layer or my repository layers?
Is there a best practise to store the information which locale is sent by the request, so that I can access it in my services and repositories? I think the value must be request/thread specific. Giving it as a method parameter down to the other layer seems pretty ugly.
Thank you
Regards form Germany
I'm using Spring Boot 1.3.5 with Rest Controllers and everything is working fine.
I am also using Spring's validation sample techniques from the official documentation (JSR-303 Bean Validation API and Spring's validator interface, i tried both and faced the same problem) and the validations are working, but I am not able to configure custom messages.
I have configured a messages.properties file, and I can access the messages on this file just fine. However this validation seems not to be capable of reading or accessing my messages source (messages.properties) configured automatically via spring boot.
I can access the messages directly from the messages source object injected in controller via #Autowired (there's a comment in the code). However, the binding result of the Spring's validator interface or the JSR-303 Bean Validation seems to not be capable of accessing the messages.properties loaded in MessageSource. The result I have is that my errors have codes but don't have default messages.
Here is my Application class:
#SpringBootApplication
#ImportResource({ "classpath:security/cas-context.xml", "classpath:security/cas-integration.xml",
"classpath:security/security.xml" })
#EnableAutoConfiguration(exclude = VelocityAutoConfiguration.class) // http://stackoverflow.com/questions/32067759/spring-boot-starter-cache-velocity-is-missing
public class Application extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public ServletRegistrationBean cxfServlet() {
return new ServletRegistrationBean(new CXFServlet(), "/services/*");
}
#Bean(name = Bus.DEFAULT_BUS_ID)
public SpringBus springBus() {
return new SpringBus();
}
#Bean
public Nfse nfseService() {
return new NfseImpl();
}
#Bean
public Endpoint endpoint() {
EndpointImpl endpoint = new EndpointImpl(springBus(), nfseService());
endpoint.publish("/nfseSOAP");
return endpoint;
}
}
Here is my Bean:
public class Protocolo {
private Long id;
#NotNull
#Min(1)
#Max(1)
private String protocolo;
private StatusProtocoloEnum status;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getProtocolo() {
return protocolo;
}
public void setProtocolo(String protocolo) {
this.protocolo = protocolo;
}
public StatusProtocoloEnum getStatus() {
return status;
}
public void setStatus(StatusProtocoloEnum status) {
this.status = status;
}
}
Here is My rest controller:
#RestController
public class ProtocoloController {
#Autowired
private MessageSource messageSource;
#Autowired
private ProtocoloDAO protocoloDAO;
#RequestMapping(value = "/prot", method = RequestMethod.POST)
public void testar(#Valid #RequestBody Protocolo p) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
System.out.println(auth.getAuthorities());
System.out.println(messageSource.getMessage("protocolo.tamanho", null, null));
// IN THIS PART I'M ABLE TO PRINT THE MESSAGE IF VALIDATION IS DISABLED
System.out.println(p.getProtocolo());
}
}
So, this code works fine and the method is not called since i'm calling the method with a invalid Protocolo. However, my angularJS client receives the response with the errors codes populated but with all the default messages empty since the validation is not seeing my loaded messages.properties.
Is there a way to make my Spring validation Interfaces or JSR-303 validation incorporate the loaded message.properties (messagesource) in spring boot ? How can i correct this ? If it's necessary i can paste my code sample of Spring Validation interfaces also.
Thank's a lot,
Tarcísio.
TEST CODE:
#RestController
public class ProtocoloController {
#Autowired
private MessageSource messageSource;
#Autowired
private ProtocoloDAO protocoloDAO;
#RequestMapping(value = "/prot", method = RequestMethod.POST)
public void testar(#Valid #RequestBody Protocolo p, BindingResult bindingResult) {
System.out.println(messageSource.getMessage("Min.protocolo.protocolo", null, null));
if (bindingResult.hasErrors()) {
System.out.println(bindingResult.getFieldError().getDefaultMessage());
System.out.println(bindingResult.getFieldError().getCode());
}
System.out.println(p.getProtocolo());
}
}
Edit:
Known Bug in Spring Boot 1.5.3 see https://github.com/spring-projects/spring-boot/issues/8979
In Spring Boot since 1.5.3 you need to do this
#Configuration
public class ValidationMessageConfig {
#Bean
public LocalValidatorFactoryBean mvcValidator(MessageSource messageSource) {
LocalValidatorFactoryBean factory = new LocalValidatorFactoryBean();
factory.setValidationMessageSource(messageSource);
return factory;
}
}
and then it will work.
With version 1.5.2 and before you can extend WebMVcConfigurerAdapter
#Configuration
public class ProfileMvcConfig extends WebMvcConfigurerAdapter {
private MessageSource messageSource;
#Autowired
public ProfileMvcConfig(MessageSource messageSource) {
this.messageSource = messageSource;
}
/**
* This method is overridden due to use the {#link MessageSource message source} in bean validation.
*
* #return A Validator using the {#link MessageSource message source} in bean validation.
*/
#Override
public Validator getValidator() {
LocalValidatorFactoryBean factory = new LocalValidatorFactoryBean();
factory.setValidationMessageSource(messageSource);
return factory;
}
}
also see the documentation
In Spring Boot applicaton MessageSource is configured with a MessageSourceAutoConfiguration and you don't need to autowire it. For jsr303, create proper key-value pair in the messages.properties file. For "protocolo" field, you should have following values in property file.
NotNull.protocolo.protocolo=Field cannot be left blank
Min.protocolo.protocolo=Minimum value must be {1}
You can also check messages from property file like below in your code.
public void testar(#Valid #RequestBody Protocolo p,BindingResult bindingResult) {
if(bindingResult.hasErrors()) {
System.out.println(bindingResult.getFieldError().getDefaultMessage());
}
}
you should have following values in property file:
Min.protocolo.protocolo=Minimum value must be {1}
then in the controller you obtain the message by calling function getMessage from messageSource object
Test code:
#RequestMapping(value = "/prot", method = RequestMethod.POST)
public void testar(#Valid #RequestBody Protocolo p, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
bindingResult.getFieldErrors().forEach(fieldError ->
System.out.println(messageSource.getMessage(fieldError, Locale.getDefault()))
);
}
System.out.println(p.getProtocolo());
}
I solved this in custom message in Spring validation read the last part of my answer.
Check this example as well.
I used a custom validator with custom annotation. I needed to change code in my custom validator.
public class PersonValidator implements ConstraintValidator {
#Override
public boolean isValid(final Person person, final ConstraintValidatorContext context) {
if (somethingIsInvalid()) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("Something is invalid.").addConstraintViolation();
return false;
}
return true;
}
}
This question already has answers here:
Does Spring MessageSource Support Multiple Class Path?
(7 answers)
Closed 6 years ago.
I have one maven spring project (as a library) and I am adding it as a dependency into spring maven project(web service).
I have some properties files in both projects. In library there is one validationmessages.properties file.
I am using hibernate validator annotations on my model.
For example,
#NotBlank(message = "{NotBlank-entityId}")
Private String entityId;
The class model which is in library,using as a request body in webservice, here library hibernate validation messages are not working in webservice.
Here's the code:
Model:
Task.java (In library)
public class Task {
#NotBlank(message = "{NotNull-EntityID}")
private String entityId;
public String getEntityId() {
return entityId;
}
public void setEntityId(final String entityId) {
this.entityId = entityId;
}
}
Taskvalidationmessages.properties (In library)
NotNull-EntityID = Entity ID can not be null.
TaskManagementConfiguration.java (In library)
#Configuration
#PropertySources({ #PropertySource("classpath:queries.properties"),
#PropertySource("classpath:Taskvalidationmessages.properties") })
#Import(DataSourceConfiguration.class)
public class TaskManagementConfiguration {
}
TaskResource.java (Controller in webservice project)
#RequestMapping(value = WebserviceConstant.CREATE_TASK, method = RequestMethod.POST, produces = WebserviceConstant.APPLICATION_JSON)
public ResponseEntity<CreateTaskResponse> createTask(
#Valid #RequestBody final Task request,
#RequestHeader(value = "access-token") final String accessToken) {
return new ResponseEntity<CreateTaskResponse>(
taskService.createTask(request, receivedToken), HttpStatus.CREATED);
}
App.java (In Web service project)
#Configuration
#SpringBootApplication
#PropertySources({ #PropertySource("classpath:user-queries.properties") })
#Import({ TaskManagementConfiguration.class })
public class App {
public static void main(final String[] args) {
SpringApplication.run(App.class, args);
}
}
Whenever I hit the resource url with empty value of entityId.
It gives error like:
{
"fieldErrors": [
{
"field": "entityId",
"code": 200,
"message": "{NotNull-EntityID}"
}
]
}
You need org.springframework.validation.beanvalidation.LocalValidatorFactoryBean
Add validator bean in your configurations (TaskManagementConfiguration)
#Bean(name = "messageSource")
public MessageSource messageSource()
{
ReloadableResourceBundleMessageSource bean = new ReloadableResourceBundleMessageSource();
bean.setBasename("classpath:Taskvalidationmessages");
bean.setDefaultEncoding("UTF-8");
return bean;
}
#Bean(name = "validator")
public LocalValidatorFactoryBean validator()
{
LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
bean.setValidationMessageSource(messageSource());
return bean;
}
I got a working spring boot rest service. When the path is wrong it doesn't return anything. No response At all. At the same time it doesn't throw error either. Ideally I expected a 404 not found error.
I got a GlobalErrorHandler
#ControllerAdvice
public class GlobalErrorHandler extends ResponseEntityExceptionHandler {
}
There is this method in ResponseEntityExceptionHandler
protected ResponseEntity<Object> handleNoHandlerFoundException(NoHandlerFoundException ex, HttpHeaders headers,
HttpStatus status, WebRequest request) {
return handleExceptionInternal(ex, null, headers, status, request);
}
I have marked error.whitelabel.enabled=false in my properties
What else must I do for this service to throw a 404 not found response back to clients
I referred a lot of threads and don't see this trouble faced by anybody.
This is my main application class
#EnableAutoConfiguration // Sprint Boot Auto Configuration
#ComponentScan(basePackages = "com.xxxx")
#EnableJpaRepositories("com.xxxxxxxx") // To segregate MongoDB
// and JPA repositories.
// Otherwise not needed.
#EnableSwagger // auto generation of API docs
#SpringBootApplication
#EnableAspectJAutoProxy
#EnableConfigurationProperties
public class Application extends SpringBootServletInitializer {
private static Class<Application> appClass = Application.class;
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(appClass).properties(getProperties());
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public FilterRegistrationBean correlationHeaderFilter() {
FilterRegistrationBean filterRegBean = new FilterRegistrationBean();
filterRegBean.setFilter(new CorrelationHeaderFilter());
filterRegBean.setUrlPatterns(Arrays.asList("/*"));
return filterRegBean;
}
#ConfigurationProperties(prefix = "spring.datasource")
#Bean
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
static Properties getProperties() {
Properties props = new Properties();
props.put("spring.config.location", "classpath:/");
return props;
}
#Bean
public WebMvcConfigurerAdapter webMvcConfigurerAdapter() {
WebMvcConfigurerAdapter webMvcConfigurerAdapter = new WebMvcConfigurerAdapter() {
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false).favorParameter(true).parameterName("media-type")
.ignoreAcceptHeader(false).useJaf(false).defaultContentType(MediaType.APPLICATION_JSON)
.mediaType("xml", MediaType.APPLICATION_XML).mediaType("json", MediaType.APPLICATION_JSON);
}
};
return webMvcConfigurerAdapter;
}
#Bean
public RequestMappingHandlerMapping defaultAnnotationHandlerMapping() {
RequestMappingHandlerMapping bean = new RequestMappingHandlerMapping();
bean.setUseSuffixPatternMatch(false);
return bean;
}
}
The solution is pretty easy:
First you need to implement the controller that will handle all error cases. This controller must have #ControllerAdvice -- required to define #ExceptionHandler that apply to all #RequestMappings.
#ControllerAdvice
public class ExceptionHandlerController {
#ExceptionHandler(NoHandlerFoundException.class)
#ResponseStatus(value= HttpStatus.NOT_FOUND)
#ResponseBody
public ErrorResponse requestHandlingNoHandlerFound() {
return new ErrorResponse("custom_404", "message for 404 error code");
}
}
Provide exception you want to override response in #ExceptionHandler. NoHandlerFoundException is an exception that will be generated when Spring will not be able to delegate request (404 case). You also can specify Throwable to override any exceptions.
Second you need to tell Spring to throw exception in case of 404 (could not resolve handler):
#SpringBootApplication
#EnableWebMvc
public class Application {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(Application.class, args);
DispatcherServlet dispatcherServlet = (DispatcherServlet)ctx.getBean("dispatcherServlet");
dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);
}
}
Result when I use non defined URL
{
"errorCode": "custom_404",
"errorMessage": "message for 404 error code"
}
UPDATE: In case you configure your SpringBoot application using application.properties then you need to add the following properties instead of configuring DispatcherServlet in main method (thanks to #mengchengfeng):
spring.mvc.throw-exception-if-no-handler-found=true
spring.web.resources.add-mappings=false
I know this is an old question but here is another way to configure the DispatcherServlet in code but not in the main class. You can use a separate #Configuration class:
#EnableWebMvc
#Configuration
public class ExceptionHandlingConfig {
#Autowired
private DispatcherServlet dispatcherServlet;
#PostConstruct
private void configureDispatcherServlet() {
dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);
}
}
Please not that this does not work without the #EnableWebMvc annotation.
Add this to your Properties file.
spring:
mvc:
throw-exception-if-no-handler-found: true
web:
resources:
add-mappings: false
In your #ControllerAdvice class add this:
#ExceptionHandler(NoHandlerFoundException.class)
public ResponseEntity<Object> handleNoHandlerFound404() {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);;
}
My app is fully-configured spring-boot app with thymeleaf templates engine. I18n configured as well so I can use it within my templates. Here is the config I use:
spring.messages.basename=i18n/messages
While manual validating fields I18n also work fine:
BindingResult result;
result.rejectValue("field", "some.i18n.code");
But once I want to implement some custom ConstraintValidator objects and use message field - no I18n involved, I receive plain codes as a response instead of a message. I.e.
{some.i18n.code}
I tried this solution - no result.
This on as well - same result.
What am I missing?
I guess I found the solution, maybe it will be helpful to others. All you have to do is to add the following definitions into your WebMvcConfigurerAdapter configuration implementation:
#Autowired
private MessageSource messageSource;
#Bean
public LocalValidatorFactoryBean validator() {
LocalValidatorFactoryBean validatorFactoryBean = new LocalValidatorFactoryBean();
validatorFactoryBean.setValidationMessageSource(messageSource);
return validatorFactoryBean;
}
#Override
public Validator getValidator() {
return validator();
}
An alternative solution is just declare this bean in any of your #Configuration classes:
#Bean
public LocalValidatorFactoryBean localValidatorFactoryBean(MessageSource messageSource) {
LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
bean.setValidationMessageSource(messageSource);
return bean;
}
Due to declaring this, now my custom error codes from my custom validators are being searched for in my messages.properties (that I also have in a i18n subdirectory by declaring spring.messages.basename=i18n/messages).
Example validator code:
public class ContestValidator implements ConstraintValidator<ValidContest, CreateContestParameters> {
#Override
public void initialize(ValidContest constraintAnnotation) {
}
#Override
public boolean isValid(CreateContestParameters contestParameters, ConstraintValidatorContext context) {
boolean result = true;
if (!endDateIsEqualOrAfterStartDate(contestParameters)) {
context.buildConstraintViolationWithTemplate("{Contest.endDate.invalid}")
.addPropertyNode("endDate").addConstraintViolation();
result = false;
}
if (!registrationDeadlineIsBeforeStartDate(contestParameters)) {
context.buildConstraintViolationWithTemplate("{Contest.registrationDeadline.invalid}")
.addPropertyNode("registrationDeadline").addConstraintViolation();
}
return result;
}
}