Quantcast
Channel: Baeldung
Viewing all articles
Browse latest Browse all 4536

Differences in @Valid and @Validated Annotations in Spring

$
0
0

1. Overview

In this quick tutorial, we'll focus on the differences between @Valid and @Validated annotations in Spring.

Validating users' input is a common functionality in most of our applications. In the Java Ecosystem, we specifically use the Java Standard Bean Validation API to support this. Moreover, this is also well integrated with Spring from version 4.0 onwards. The @Valid and @Validation annotations stem from this Standard Bean API.

In the next sections, let's look at them in detail.

2. @Valid and @Validation Annotations

In Spring, we use JSR-303's @Valid annotation for method level validation. Moreover, we also use it to mark a member attribute for validation. However, this annotation doesn't support group validation.

Groups help to limit the constraints applied during validation. One particular use case is UI wizards. Here, in the first step, we may have a certain sub-group of fields. In the subsequent step, there may be another group belonging to the same bean. Hence we need to apply constraints on these limited fields in each step, but @Valid doesn't support this.

In this case, for group-level, we have to use Spring's @Validation, which is a variant of this JSR-303's @Valid.  This is used at the method-level. And for marking member attributes, we continue to use the @Valid annotation.

Now, let's dive right in and look at the usage of these annotations with an example.

3. Example

Let's consider a simple user registration form developed using Spring Boot. To begin with, we'll have only the name and the password attributes:

public class UserAccount {
    @NotNull
    @Size(min = 4, max = 15)
    private String password;
    @NotBlank
    private String name;
    // standard constructors / setters / getters / toString
     
}

Next, let's look at the controller. Here, we'll have the saveBasicInfo method with the @Valid annotation to validate the user input:

@RequestMapping(value = "/saveBasicInfo", method = RequestMethod.POST)
public String saveBasicInfo(
  @Valid @ModelAttribute("useraccount") UserAccount useraccount, 
  BindingResult result, 
  ModelMap model) {
    if (result.hasErrors()) {
        return "error";
    }
    return "success";
}

Now let's test this method :

@Test
public void givenSaveBasicInfo_whenCorrectInput_thenSuccess() throws Exception {
    this.mockMvc.perform(MockMvcRequestBuilders.post("/saveBasicInfo")
      .accept(MediaType.TEXT_HTML)
      .param("name", "test123")
      .param("password", "pass"))
      .andExpect(view().name("success"))
      .andExpect(status().isOk())
      .andDo(print());
}

After confirming that the test runs successfully, let's now extend the functionality. The next logical step is to convert this to a multi-step registration form, as is the case with most wizards. The first step with the name and the password remains unchanged. In the second step, we'll fetch additional information like age and phone. Hence we'll update our domain object with these additional fields:

public class UserAccount {
    
    @NotNull
    @Size(min = 4, max = 15)
    private String password;
 
    @NotBlank
    private String name;
 
    @Min(value = 18, message = "Age should not be less than 18")
    private int age;
 
    @NotBlank
    private String phone;
    
    // standard constructors / setters / getters / toString   
    
}

However, this time we'll notice that the previous test fails. This is because we aren't passing in the age and phone fields, which are still not in the picture on the UI. To support this behavior, we'll need group validation and the @Validated annotation.

For this, we need to group the fields creating two distinct groups. First, we'll need to create two marker interfaces. A separate one for each group or each step. We can refer to our article on group validation for the exact implementation of this. Here, let's focus on the differences in the annotations.

We'll have the BasicInfo interface for the first step and the AdvanceInfo for the second step. Furthermore, we'll update our UserAccount class to use these marker interfaces as follows:

public class UserAccount {
    
    @NotNull(groups = BasicInfo.class)
    @Size(min = 4, max = 15, groups = BasicInfo.class)
    private String password;
 
    @NotBlank(groups = BasicInfo.class)
    private String name;
 
    @Min(value = 18, message = "Age should not be less than 18", groups = AdvanceInfo.class)
    private int age;
 
    @NotBlank(groups = AdvanceInfo.class)
    private String phone;
    
    // standard constructors / setters / getters / toString   
    
}

In addition, we'll now update our controller to use the @Validated annotation instead of @Valid:

@RequestMapping(value = "/saveBasicInfoStep1", method = RequestMethod.POST)
public String saveBasicInfoStep1(
  @Validated(BasicInfo.class) 
  @ModelAttribute("useraccount") UserAccount useraccount, 
  BindingResult result, ModelMap model) {
    if (result.hasErrors()) {
        return "error";
    }
    return "success";
}

As a result of this update, our test now runs successfully. Now let's also test this new method:

@Test
public void givenSaveBasicInfoStep1_whenCorrectInput_thenSuccess() throws Exception {
    this.mockMvc.perform(MockMvcRequestBuilders.post("/saveBasicInfoStep1")
      .accept(MediaType.TEXT_HTML)
      .param("name", "test123")
      .param("password", "pass"))
      .andExpect(view().name("success"))
      .andExpect(status().isOk())
      .andDo(print());
}

This too runs successfully. Hence we can see how the usage of @Validation is essential for group validation.

Next, let's see how @Valid is essential to trigger the validation of nested attributes.

4. Using @Valid Annotation to Mark Nested Objects

The @Valid annotation is used to mark nested attributes in particular. This triggers the validation of the nested object. For instance, in our current scenario, let's create a UserAddress object:

public class UserAddress {
    @NotBlank
    private String countryCode;
    // standard constructors / setters / getters / toString
}

To ensure validation of this nested object, we'll decorate the attribute with the @Valid annotation:

public class UserAccount {
    
    //...
    
    @Valid
    @NotNull(groups = AdvanceInfo.class)
    private UserAddress useraddress;
    
    // standard constructors / setters / getters / toString 
}

5. Pros and Cons

Let’s look at some of the pros and cons of using @Valid and @Validated annotations in Spring.

The @Valid annotation ensures the validation of the whole object. Importantly, it performs the validation of the whole object graphs. However, this creates issues for scenarios needing only partial validation.

On the other hand, we can use @Validation for group validation, including the above partial validation. However, in this instance, the validated entities have to know the validation rules for all the groups or use-cases it is used in. Here we're mixing concerns, hence this can result in an anti-pattern.

6. Conclusion

In this quick tutorial, we explored the key differences between @Valid and @Validated Annotations.

To conclude, for any basic validation, we'll use the JSR @Valid annotation in our method calls. On the other hand, for any group validation, including group sequences, we'll need to use Spring's @Validation annotation in our method call. The @Valid annotation is also needed to trigger the validation of nested properties.

As always, the code presented in this article is available over on GitHub.

The post Differences in @Valid and @Validated Annotations in Spring first appeared on Baeldung.

        

Viewing all articles
Browse latest Browse all 4536

Trending Articles