Custom Messages in Bean Validation using Spring’s Validator interface

Tarcísio Filó picture Tarcísio Filó · Sep 15, 2016 · Viewed 12.4k times · Source

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());
}

}

Answer

Tobsch picture Tobsch · Apr 21, 2017

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