You might have a business requirement to validate a form. But that doesn't necessarily mean that you want to validate the whole thing at once.

That's especially the case with so-called "wizard" forms that consist of multiple sections.

With wizards (or "stepper" forms in Angular Material), you might just want to validate specific sections while leaving alone the rest of the form.

In this guide, I'll show you how to do that in Spring Boot with the @Validated annotation.

As always, feel free to grab the source code on GitHub. Alternatively, you can follow along here.

The Business Requirements

Your boss Smithers walks into your office, sits down, and stares at you.

"About that CRM app you're working on," he says. "I need you to add validation to the contact entry page."

He shuffles awkwardly in the chair, leading you to think he has a bad case of hemorrhoids.

"I know it's a wizard form, but I don't want you to validate the whole thing at once," Smithers continues. "Just validate each section, one at a time."

He stands up with a noticeable "ouch" emanating from his mouth and walks out.

Starting With @Valid

Before I go over @Validated, it's important to distinguish it from @Valid.

In a nutshell, @Valid will validate the whole object at once. It's not what you want to use in this case.

@Validated, on the other hand, lets you designate specific fields in an object as part of a group. Then, you can validate just the fields that belong to that specific group.

That's what you'll need to use here.

But first, let's start by using @Valid to demonstrate the differences. Here's a lightweight version of the contact model.

public class Contact {

    @NotBlank(message = "Please provide a first name")
    @Size(max = 50, message = "First name must be between 1 and 50 characters")
    private String firstName;
	
    @NotBlank(message = "Please provide a last name")
    @Size(max = 50, message = "Last name must be between 1 and 50 characters")
    private String lastName;
	
	
    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 toString() {
		return ReflectionToStringBuilder.toString(this);
    }
}

That's a basic POJO with some validation logic. If you'd like to learn more about validating data with Spring Boot and forms, feel free to take a look at my other tutorial on that subject.

The validation itself isn't that difficult. In this case, the user just needs to enter a first name and last name that are no longer than 50 characters in length.

And here's what the controller looks like:

@RestController
@RequestMapping("/contact")
public class ContactsController {
    
    private static final Logger LOG = LoggerFactory.getLogger(ContactsController.class);
            
    
    @PostMapping("")
    public ResponseEntity<?> createContact(@Valid @RequestBody Contact contact) {
        LOG.debug("Creating new contact: " + contact);
        
        
        return ResponseEntity.status(HttpStatus.CREATED).body(contact);
    }
    
}

The single method you see there accepts a POST request to /contact.  That @RequestBody annotation you see in the method signature indicates that the endpoint will consume a JSON request with properties that match the Contact class you just looked at.

A valid JSON payload looks like this:

{"firstName" : "Johnny", "lastName" : "Doe"}

Speaking of valid, the @Valid annotation in front of @RequestBody ensures that the payload complies with the rules you just saw in the Contact class.

With all of that in mind, you can run a couple of little tests to make sure everything is up to snuff:

@SpringBootTest
@AutoConfigureMockMvc
public class ValidatedWebAppTest {

    @Autowired
    private MockMvc mockMvc;
    
    
    @Test
    public void testCreateContactWithGoodData() throws Exception {
        this.mockMvc
            .perform(MockMvcRequestBuilders.post("/contact")
            .contentType(MediaType.APPLICATION_JSON)
            .content("{\"firstName\" : \"Johnny\", \"lastName\" : \"Doe\"}"))
            .andDo(MockMvcResultHandlers.print())
            .andExpect(MockMvcResultMatchers.status().is(HttpStatus.CREATED.value()));
    }
    
    
    @Test
    public void testCreateContactWithMissingLastName() throws Exception {
        this.mockMvc
            .perform(MockMvcRequestBuilders.post("/contact")
            .contentType(MediaType.APPLICATION_JSON)
            .content("{\"firstName\" : \"Johnny\"}"))
            .andDo(MockMvcResultHandlers.print())
            .andExpect(MockMvcResultMatchers.status().is(HttpStatus.BAD_REQUEST.value()));
    }
}

The first test makes sure that you get a proper return (an HTTP status of "Created" in this case) when you POST valid data to the /contact endpoint.

The second test makes sure you get a "Bad Request" response when you try to POST a body that leaves out the last name.

Both of those tests pass with flying colors. But that's not what Smithers is looking for.

Giving Partial Credit

What you need to do now is allow someone to validate only a single section of the wizard while disregarding validation rules for the other sections.

For that, you need to use @Validated instead of @Valid.

So let's do a little bit of refactoring to support the requirement. Here's the new Contact class:

public class Contact {

    public static interface StepOne {
        
    }
    
    public static interface StepTwo {
        
    }
    
    
    @NotBlank(message = "Please provide a first name", groups = StepOne.class)
    @Size(max = 50, message = "First name must be between 1 and 50 characters", groups = StepOne.class)
    private String firstName;
	
    @NotBlank(message = "Please provide a last name", groups = StepOne.class)
    @Size(max = 50, message = "Last name must be between 1 and 50 characters", groups = StepOne.class)
    private String lastName;
	
    @NotBlank(message = "Please provide an email address", groups = StepTwo.class)
    @Size(max = 50, message = "Email address must be between 1 and 50 characters", groups = StepTwo.class)	
    private String email;
    
    @NotBlank(message = "Please provide a phone number", groups = StepTwo.class)
    @Size(max = 20, message = "Phone number must be between 1 and 20 characters", groups = StepTwo.class)  
    private String phone;

// getters and setters

Pay attention to the marker interfaces at the top. The code includes those because the @Validated annotation designates groups by class instead of by string or integer or some other means of reference.

As a result, lots of developers like to user marker interfaces when dealing with partial validation.

You could, alternatively, structure the object with child objects so that each child corresponds to a section on the wizard. But that means reworking your object model just to support a different means of validation. I'm not into that.

Now take a look at the validation annotations. They look the same as the last time you saw them but with one very noticeable difference. That's the addition of the groups attribute.

The first two fields use StepOne.class as the value of groups while the last two use StepTwo.class.

It should be pretty obvious what's going on here: Step 1 of the wizard asks for basic info about the person, such as his or her name. Step 2 asks about contact methods such as an email address and phone number.

But you don't need to validate the email address and phone number after Step 1. Just do that during Step 2.

Once you specify those groups, you can validate just the data required for Step 1 or just the data required for Step 2. You don't need to validate it all at once.

But you still have to change the controller.

    @PostMapping("")
    public ResponseEntity<?> createContact(@Validated(Contact.StepOne.class) @RequestBody Contact contact) {
        LOG.debug("Creating new contact: " + contact);
        
        
        return ResponseEntity.status(HttpStatus.CREATED).body(contact);
    }

And there's the @Validated annotation we've been talking about this whole time.

Note that @Validated specifies a group: it's the StepOne.class marker interface you saw just above.

Everything else stays the same.

Run that unit test again and it should light up green just like before.

Wrapping It Up

There you have it. A nice, neat way to validate only specific sections within a wizard-like form.

Now it's up to you to adapt the code you see above to your own needs. Also, create some more test cases and do a little more tinkering so you fully understand what's going on.

Remember, you can always grab the code on GitHub.

Have fun!

Photo by Cytonn Photography from Pexels