3

I am using the annotation validation as below:

    public String processRegistration(@Valid Spitter spitter, Errors errors,
            Model model) {
        if (errors.hasErrors()) {
            return "registerForm";
        }
        ...
      }

But the errors.hasErrors() always return false. I guess I didn't turn on some kind of switch for the annotation driven validation. But how can I do it with JavaConfig in Spring 4? I tried to apply the @AnnotationDrivenConfig to the configuration class, but that type cannot even be resolved.

ADD 1

I already have the following configuration in the servlet-context.xml, but still doesn't work.

<annotation-driven />

ADD 2

The spitter.java:

package com.learnspring.mvc.web;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.hibernate.validator.constraints.Email;

public class Spitter {

    private Long id;

    @NotNull
    @Size(min = 5, max = 16)
    private String username="default name";

    @NotNull
    @Size(min = 5, max = 25)
    private String password;

    @NotNull
    @Size(min = 2, max = 30)
    private String firstName;

    @NotNull
    @Size(min = 2, max = 30)
    private String lastName;

    @NotNull
    @Email
    private String email;

    public Spitter() {
    }

    public Spitter(String username, String password, String firstName,
            String lastName, String email) {
        this(null, username, password, firstName, lastName, email);
    }

    public Spitter(Long id, String username, String password, String firstName,
            String lastName, String email) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.firstName = firstName;
        this.lastName = lastName;
        this.email = email;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    @Override
    public boolean equals(Object that) {
        return EqualsBuilder.reflectionEquals(this, that, "firstName",
                "lastName", "username", "password", "email");
    }

    @Override
    public int hashCode() {
        return HashCodeBuilder.reflectionHashCode(this, "firstName",
                "lastName", "username", "password", "email");
    }

}

The SpitterController.java

package com.learnspring.mvc.web;

import static org.springframework.web.bind.annotation.RequestMethod.*;

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

import com.learnspring.mvc.web.Spitter;
import com.learnspring.mvc.web.SpitterRepository;

@Controller
@RequestMapping("/spitter")
public class SpitterController {

    private SpitterRepository spitterRepository;

    @Autowired
    public SpitterController(SpitterRepository spitterRepository) {
        this.spitterRepository = spitterRepository;
    }

    @RequestMapping(value = "/register", method = GET)
    public String showRegistrationForm(Model model) {
        model.addAttribute("spitter", new Spitter());

        return "registerForm";
    }

    @RequestMapping(value = "/register", method = POST)
    public String processRegistration(@Valid Spitter spitter,
            Errors errors, Model model) {


        if (errors.hasErrors()) {
            return "registerForm";
        }

        if (spitter == null) {
            model.addAttribute("ufo", "spitter is null!");
            model.addAttribute("mark", "MARKER");
            return "forward:/spitter/spitter/registerFail";
        }

        else if (!spitter.getUsername().contains("ufo")) {
            model.addAttribute("ufo", "spitter user name is not ufo!!");
            model.addAttribute("mark", "MARKER:" + spitter.getUsername());
            model.addAttribute("pwd", "MARKER:" + spitter.getPassword());
            return "forward:/spitter/spitter/registerFail";
        }

        else
            return "redirect:/spitter/spitter/registerOK";

    }

    @RequestMapping(value = "/{username}", method = GET)
    public String showSpitterProfile(@PathVariable String username, Model model) {
        Spitter spitter = spitterRepository.findByUsername(username);
        model.addAttribute(spitter);
        return "profile";
    }

    @RequestMapping(value = "/spitter/registerOK", method = GET)
    public String showRegisterOK() {
        return "registerOK";
    }

    @RequestMapping(value = "/spitter/registerFail", method = POST)
    public String showRegisterFail() {
        return "registerFail";
    }

}

The registerForm.jsp:

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="f" %>
<%@ page session="false" %>
<html>
  <head>
    <title>Spitter</title>
    <link rel="stylesheet" type="text/css" 
          href="<c:url value="/resources/style.css" />" >
  </head>
  <body>
    <h1>Register</h1>

    <f:form method="POST" commandName="spitter">
      f:First Name: <f:input path="firstName" /><br/>
      f-Last Name: <f:input path="lastName" /><br/>
      f-Email: <f:input path="email" /><br/>
      f-User Name: <f:input path="username" /><br/>
      f-Password: <f:input path="password" /><br/>
      <input type="submit" value="Register" />
    </f:form>
  </body>
</html>

The WebConfig.java:

package com.learnspring.mvc.web;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

@Configuration
@EnableWebMvc
@ComponentScan("com.learnspring.mvc.web")
public class WebConfig extends WebMvcConfigurerAdapter {

    @Bean
    public ViewResolver viewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".jsp");
        return resolver;
    }

    @Override
    public void configureDefaultServletHandling(
            DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        // TODO Auto-generated method stub
        super.addResourceHandlers(registry);
    }

}

The RootConfig.java

package com.learnspring.mvc.config;

import java.util.regex.Pattern;

import com.learnspring.mvc.config.RootConfig.WebPackage;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.Import;
import org.springframework.core.type.filter.RegexPatternTypeFilter;

@Configuration
@ComponentScan(basePackages = { "com.learnspring.mvc" }, excludeFilters = { @Filter(type = FilterType.CUSTOM, value = WebPackage.class) })
public class RootConfig {
    public static class WebPackage extends RegexPatternTypeFilter {
        public WebPackage() {
            super(Pattern.compile("com.learnspring.mvc\\.web"));
        }
    }
}

The SpitterWebInitialization.java

package com.learnspring.mvc.config;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

import com.learnspring.mvc.config.RootConfig;
import com.learnspring.mvc.web.WebConfig;

public class SpitterWebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

  @Override
  protected Class<?>[] getRootConfigClasses() {
    return new Class<?>[] { RootConfig.class };
  }

  @Override
  protected Class<?>[] getServletConfigClasses() {
    return new Class<?>[] { WebConfig.class };
  }

  @Override
  protected String[] getServletMappings() {
    return new String[] { "/" };
  }

}

ADD 3

And the web.xml: (Actually, everything is commented out.)

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

    <!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
    <!--
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring/root-context.xml</param-value>
    </context-param>
      -->
    <!-- Creates the Spring Container shared by all Servlets and Filters -->
    <!-- 
    <listener>
          <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
     -->

    <!-- Processes application requests -->
    <!--
    <servlet>
        <servlet-name>appServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>appServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    -->

</web-app>
smwikipedia
  • 61,609
  • 92
  • 309
  • 482
  • In this guide http://spring.io/guides/gs/validating-form-input/ they use BindingResult instead of Errors. Try that. – Evgeni Dimitrov Jan 11 '15 at 10:35
  • @Evgeni The `BindingResult` actually inherits the `Errors` interface. As I tried, changing to it doesn't solve the issue. – smwikipedia Jan 11 '15 at 11:48
  • Make sure you have `hibernate-validator` in your class path also make sure that you are submitting invalid values. – M. Deinum Jan 11 '15 at 12:35
  • @M.Deinum Thanks for the reminding. I am using `validation-api-1.0.0.GA` and `hibernate-validator-4.2.0.Final.jar`. And the input is invalid for sure. But still the same issue. – smwikipedia Jan 11 '15 at 12:56
  • @M.Deinum I posted it. Thanks. – smwikipedia Jan 11 '15 at 13:07
  • Why do you have both xml and java config? Your xml should be basically useless as you do everything with java config? Do you have a web.xml? – M. Deinum Jan 11 '15 at 15:41
  • @M.Deinum Yes, I have a web.xml. Actually, I have 3 config files, `web.xml`, `root-context.xml` and `servlet-context.xml`. I just added the web.xml to my question. I removed the `root-context.xml` and `servlet-context.xml`. And commented out all the spring-related things in `web.xml`. Still doesn't work. – smwikipedia Jan 12 '15 at 00:45
  • Remove your web.xml. Is your application even starting? According to the specs it shouldn't as your web.xml is set to version 2.5 instead of 3.0 which should disable the java based registration of servlets (but that might vary from server to server ho strickt this is). – M. Deinum Jan 12 '15 at 06:37
  • @M.Deinum Yes the web app can start. Anyway I deleted the web.xml. But the validation still doesn't work. – smwikipedia Jan 12 '15 at 06:56
  • Add `@ModelAttribute` next to `@Valid`. – M. Deinum Jan 12 '15 at 07:00
  • @M.Deinum I changed it to this `@ModelAttribute("spitter") @Valid Spitter spitter` in the controller method signature. But it still doesn't work. – smwikipedia Jan 12 '15 at 08:39
  • Then it really looks like you don't have the validator api on the classpath or that you have an incompatible version. Enable debugging and check if the `LocalValidatorFactoryBean` is created. Try to add a field of type `Validator` to your controller and put `@Autowired` on it and check the type. – M. Deinum Jan 12 '15 at 09:31
  • @M.Deinum I created a field `@Autowired private Validator localValidator;` in the controller. And my application throws the exception when the Tomcat starts: `java.lang.ClassNotFoundException: javax.validation.Validator`. I am using `validation-api-1.1.0.Final.jar` and `hibernate-validator-5.0.1.Final.jar`. I checked both jar are valid zip files. – smwikipedia Jan 12 '15 at 11:39
  • I meant the `org.springframework.validation.Validator` not the one from the `javax.validation` package. But the class not found means that those aren't on your runtime classpath. The fact that they work at dev time doesn't mean thay are available at runtime. What is the scope of those jars? I assume you are using maven? – M. Deinum Jan 12 '15 at 11:41
  • @M.Deinum I changed the field to `@Autowired private org.springframework.validation.Validator localValidator;`. No exception this time. The localValidator's type is `org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport$NoOpValidator`. I am not using Maven. I am using a user library which directly reference the `validation-api-1.1.0.Final.jar`. But the jar file is on the `classpath` of the Tomcat server. – smwikipedia Jan 12 '15 at 11:48
  • Judging from the stacktrace it isn't. Make sure it is exported to the war file in the WEB-INF/lib directory (I wouldn't add it to tomcat). And I would strongly suggest maven or gradle (or whatever) to manage your dependencies and to build your system, you don't want it dependend on what someone has on its build path. – M. Deinum Jan 12 '15 at 11:53
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/68647/discussion-between-smwikipedia-and-m-deinum). – smwikipedia Jan 12 '15 at 11:58

2 Answers2

5

Judging from your explanation and the following error java.lang.ClassNotFoundException: javax.validation.Validator Spring doesn't see the classes and as such doesn't enable JSF-303 validation.

Make sure that the correct jars are on the classpath and that you have an implementation as well. When using maven adding something like the following should do the trick.

<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>1.1.0.Final</version>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>5.1.3.Final</version>
</dependency>

This will add the needed jars to the WEB-INF/lib directory which in turn lets Spring Web MVC detect it and configure the appropriate bean.

For an explanations of the different annotations you might want to check In Hibernate Validator 4.1+, what is the difference between @NotNull, @NotEmpty, and @NotBlank?.

Community
  • 1
  • 1
M. Deinum
  • 115,695
  • 22
  • 220
  • 224
  • Many thanks for your patient explanation and great debugging skills! It turns out I don't have such 2 libraries in my WAR package. Maven can help me to include the 2 dependency jars into the exported WAR file. But I am wondering if it is possible to export a `User Library` as well because I want to use it as a container for my commonly used JARs. – smwikipedia Jan 12 '15 at 13:58
  • Your build (and deliverable) should be reliable and not depend on the accidentally correctly configured Runtime environment of the user. If you are sure that you have added them to your server you could add them as `provided` dependencies. I would recommend against meddling with the classpath of your server it often leads to more problems then it solves (nice to debug classcast exceptions for one). – M. Deinum Jan 12 '15 at 14:04
  • I just tried to manually provide these JARs. Besides I need to place them in the right folder (WEB-INF/lib), I have to handle the transitive dependencies. It's better to delegate it to Maven. – smwikipedia Jan 12 '15 at 15:31
  • BTW, why the servlet container such as Tomcat doesn't provide such validation API? – smwikipedia Jan 12 '15 at 15:32
  • It is just a servlet container and only provides the required APIs for that. If you want the validation API as well you would need something like JBoss or another full blown app server. – M. Deinum Jan 12 '15 at 18:44
  • Thanks. I used to use Microsoft IIS and new to Java world. I was treating Tomcat as something similar to IIS. – smwikipedia Jan 13 '15 at 01:39
  • Also - you can add the api at compile scope, but the implementation can be at runtime scope - it's only needed at runtime. – John Calcote Mar 04 '21 at 04:00
0

I see two points in your code that may cause de problem.

1) Instead of <annotation-driven /> use the correct namespace <mvc:annotation-driven/>.

2) On your @Controller change your functions parameters from:

   public String processRegistration(@Valid Spitter spitter, Errors errors,
        Model model) {
    if (errors.hasErrors()) {
        return "registerForm";
    }
    ...

To:

public String processRegistration(@ModelAttribute("spitter") @Valid Spitter spitter, BindingResult result) {

    if (result.hasErrors()) {
        return "registerForm";
    }
    ...

Try it! ;)

Joabe Lucena
  • 792
  • 8
  • 21