2
votes

I'm using Java configuration for Spring MVC. I can't get Bean Validation to work. I have a domain class that I've annotated and I want to use @Valid in my Controller. I know that with XML configuration, I would set the validator this way <mvc:annotation-driven validator="validator"/>

How can I do this with Java configuration. I'm not getting any errors, the validation just doesn't work. Thanks in advance!

Here is my set up:

Domain class with the annotations:

public class Product {

    @Pattern(regexp="P[1-9]+", message="{Pattern.Product.productId.validation}")
    @ProductId 
    private String productId;

    @Size(min=4, max=50, message="{Size.Product.name.validation}")
    private String name;

    @Min(value=0, message="Min.Product.unitPrice.validation}")
    @Digits(integer=8, fraction=2, message="{Digits.Product.unitPrice.validation}")
    @NotNull(message= "{NotNull.Product.unitPrice.validation}")
    private BigDecimal unitPrice;
    private String description;
    private String manufacturer;
    private String category;
    private long unitsInStock;

Here is my Controller using @Valid:

@Controller
@RequestMapping("/products")
public class ProductController {

..... (shortened)

@RequestMapping(value = "/add", method = RequestMethod.GET)
    public String getAddNewProductForm(@ModelAttribute("newProduct") Product newProduct) {
       return "addProduct";
    }

    @RequestMapping(value = "/add", method = RequestMethod.POST)
    public String processAddNewProductForm(@ModelAttribute("newProduct") @Valid Product productToBeAdded, BindingResult result, HttpServletRequest request) {
        if(result.hasErrors()) {
            return "addProduct";
        }

        String[] suppressedFields = result.getSuppressedFields();

        if (suppressedFields.length > 0) {
            throw new RuntimeException("Attempting to bind disallowed fields: " + StringUtils.arrayToCommaDelimitedString(suppressedFields));
        }

        MultipartFile productImage = productToBeAdded.getProductImage();
        String rootDirectory = request.getSession().getServletContext().getRealPath("/");

            if (productImage!=null && !productImage.isEmpty()) {
               try {
                productImage.transferTo(new File(rootDirectory+"resources\\images\\"+productToBeAdded.getProductId() + ".png"));
               } catch (Exception e) {
                throw new RuntimeException("Product Image saving failed", e);
           }
           }


        productService.addProduct(productToBeAdded);
        return "redirect:/products";
    }

Here is my Config class with @EnableWebMVC: (***Updated to get validator***)

@SuppressWarnings("deprecation")
@Configuration
@ComponentScan(basePackages = {"com.nam.webstore"})
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

..... (shortened)

    @Bean(name = "validator")
    public LocalValidatorFactoryBean localValidatorFactoryBean() {
        LocalValidatorFactoryBean lvfb = new LocalValidatorFactoryBean();

        lvfb.setValidationMessageSource(resourceBundleMessageSource());

        return lvfb;
    }

    (***** Updated *****)
    @Override
    public Validator getValidator() {
        return localValidatorFactoryBean();
    }
}

Here is the jsp with the error tags:

..... (shortened)

<section class="container">
    <form:form  modelAttribute="newProduct" class="form-horizontal" enctype="multipart/form-data">
        <fieldset>
            <legend>Add new product</legend>

            <form:errors path="*" cssClass="alert alert-danger" element="div"/>
            <div class="form-group">
                <label class="control-label col-lg-2 col-lg-2" for="productId"><spring:message code="addProduct.form.productId.label"/></label>
                <div class="col-lg-10">
                    <form:input id="productId" path="productId" type="text" class="form:input-large"/>
                    <form:errors path="productId" cssClass="text-danger"/>
                </div>
            </div>

            <div class="form-group">
                <label class="control-label col-lg-2" for="name"><spring:message code="addProduct.form.name.label"/></label>
                <div class="col-lg-10">
                    <form:input id="name" path="name" type="text" class="form:input-large"/>
                    <form:errors path="name" cssClass="text-danger"/>
                </div>
            </div>

            <div class="form-group">
                <label class="control-label col-lg-2" for="unitPrice"><spring:message code="addProduct.form.unitPrice.label"/></label>
                <div class="col-lg-10">
                    <div class="form:input-prepend">
                        <form:input id="unitPrice" path="unitPrice" type="text" class="form:input-large"/>
                        <form:errors path="unitPrice" cssClass="text-danger"/>
                    </div>
                </div>
            </div>

            <div class="form-group">
                <label class="control-label col-lg-2" for="description"><spring:message code="addProduct.form.description.label"/></label>
                <div class="col-lg-10">
                    <form:textarea id="description" path="description" rows = "2"/>
                </div>
            </div>

Updated After setting the Logger to DEBUG, this is what I'm seeing in the console. I can see that it's firing off the validation, but I don't know why it's saying I'm returning null to the DispatcherServlet? I'm returning the view name.

Field error in object 'newProduct' on field 'unitPrice': rejected value [null]; codes [NotNull.newProduct.unitPrice,NotNull.unitPrice,NotNull.java.math.BigDecimal,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [newProduct.unitPrice,unitPrice]; arguments []; default message [unitPrice]]; default message [Unit price is Invalid. It cannot be empty.] Field error in object 'newProduct' on field 'productId': rejected value []; codes [Pattern.newProduct.productId,Pattern.productId,Pattern.java.lang.String,Pattern]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [newProduct.productId,productId]; arguments []; default message [productId],[Ljavax.validation.constraints.Pattern$Flag;@3641ef8a,P[1-9]+]; default message [Invalid product ID. It should start with character P followed by number.] Field error in object 'newProduct' on field 'name': rejected value []; codes [Size.newProduct.name,Size.name,Size.java.lang.String,Size]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [newProduct.name,name]; arguments []; default message [name],50,4]; default message [Invalid product name. It should be minimum 4 characters to maximum 50 characters long.] 2014-07-25 15:03:36 DEBUG ResponseStatusExceptionResolver:134 - Resolving exception from handler [public java.lang.String com.nam.webstore.controller.ProductController.processAddNewProductForm(com.nam.webstore.domain.Product,org.springframework.ui.ModelMap,org.springframework.validation.BindingResult,javax.servlet.http.HttpServletRequest)]: org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 3 errors Field error in object 'newProduct' on field 'unitPrice': rejected value [null]; codes [NotNull.newProduct.unitPrice,NotNull.unitPrice,NotNull.java.math.BigDecimal,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [newProduct.unitPrice,unitPrice]; arguments []; default message [unitPrice]]; default message [Unit price is Invalid. It cannot be empty.] Field error in object 'newProduct' on field 'productId': rejected value []; codes [Pattern.newProduct.productId,Pattern.productId,Pattern.java.lang.String,Pattern]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [newProduct.productId,productId]; arguments []; default message [productId],[Ljavax.validation.constraints.Pattern$Flag;@3641ef8a,P[1-9]+]; default message [Invalid product ID. It should start with character P followed by number.] Field error in object 'newProduct' on field 'name': rejected value []; codes [Size.newProduct.name,Size.name,Size.java.lang.String,Size]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [newProduct.name,name]; arguments []; default message [name],50,4]; default message [Invalid product name. It should be minimum 4 characters to maximum 50 characters long.] 2014-07-25 15:03:36 DEBUG DefaultHandlerExceptionResolver:134 - Resolving exception from handler [public java.lang.String com.nam.webstore.controller.ProductController.processAddNewProductForm(com.nam.webstore.domain.Product,org.springframework.ui.ModelMap,org.springframework.validation.BindingResult,javax.servlet.http.HttpServletRequest)]: org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 3 errors Field error in object 'newProduct' on field 'unitPrice': rejected value [null]; codes [NotNull.newProduct.unitPrice,NotNull.unitPrice,NotNull.java.math.BigDecimal,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [newProduct.unitPrice,unitPrice]; arguments []; default message [unitPrice]]; default message [Unit price is Invalid. It cannot be empty.] Field error in object 'newProduct' on field 'productId': rejected value []; codes [Pattern.newProduct.productId,Pattern.productId,Pattern.java.lang.String,Pattern]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [newProduct.productId,productId]; arguments []; default message [productId],[Ljavax.validation.constraints.Pattern$Flag;@3641ef8a,P[1-9]+]; default message [Invalid product ID. It should start with character P followed by number.] Field error in object 'newProduct' on field 'name': rejected value []; codes [Size.newProduct.name,Size.name,Size.java.lang.String,Size]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [newProduct.name,name]; arguments []; default message [name],50,4]; default message [Invalid product name. It should be minimum 4 characters to maximum 50 characters long.] 2014-07-25 15:03:36 DEBUG DispatcherServlet:1012 - Null ModelAndView returned to DispatcherServlet with name 'DispatcherServlet': assuming HandlerAdapter completed request handling 2014-07-25 15:03:36 DEBUG DispatcherServlet:991 - Successfully completed request

2

2 Answers

4
votes

In your WebMvcConfigurerAdapter you can override the getValidator() method to have it return your custom Validator.

With LocalValidatorFactoryBean, you can either call afterPropertiesSet() and getObject() to get the real Validator.

1
votes

Old subject but I had the same problem so I will paste this bean

@Override
public Validator getValidator() {
    LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
    validator.setValidationMessageSource(messageSource());
    return validator;
}