Enterprise Java

Spring Validator Validation Example

1. Introduction

Spring Validation Framework includes the Validator interface that handles data validation. It is used to implement custom validation logic. In this example, I will create a Spring MVC rest controller and implement the Spring validator interface to validate the create request.

2. Setup

In this step, I will create a gradle project along with lombok and Junit libraries.

build.gradle

plugins {
	id 'java'
	id 'org.springframework.boot' version '3.3.3'
	id 'io.spring.dependency-management' version '1.1.6'
}

group = 'org.zheng.demo.validator'
version = '0.0.1-SNAPSHOT'

java {
	toolchain {
		languageVersion = JavaLanguageVersion.of(17)
	}
}

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}


repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

tasks.named('test') {
	useJUnitPlatform()
}

3. Implement Spring Validator Interface

3.1 UserData

In this step, I will create a UserData class which has three data fields: userId, name, and email. The lombok annotations: @Data and @NoArgsConstructor are used to reduce boilerplate code.

UserData

package org.zheng.demo.validator.model;

import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
public class UserData {	
	private String userId;
	private String name;
	private String email;
}

3.2 UserDataValidator

In this step, I will create a UserDataValidator class that implements the Validator interface from the org.springframework.validation package. It overrides both supports and validate methods for the UserData class defined in step 3.1.

UserDataValidator.java

package org.zheng.demo.validator.model;

import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;

@Component
public class UserDataValidator implements Validator {

	@Override
	public boolean supports(Class<?> clazz) {
		return UserData.class.isAssignableFrom(clazz);
	}

	@Override
	public void validate(Object target, Errors errors) {
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "userId", "missing userId.");
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "missing name.");
	}
}
  • Line 12: the supports method checks if the validator can validate instances of the given class. In this example, it supports the validation at the UserData class.
  • Line 17: the validate method performs the actual validation on the given target object and records any validation errors in the given errors object. In this example, it utilizes the ValidationUtils.rejectIfEmptyOrWhitespace to validate the userId and name fields.

3.3 UserDataValidatorTest

In this step, I will create a UserDataValidatorTest class to test the validation defined in step 3.2.

UserDataValidatorTest.java

package org.zheng.demo.validator.model;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import org.junit.jupiter.api.Test;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.Errors;

class UserDataValidatorTest {
	private UserDataValidator testClass = new UserDataValidator();

	@Test
	void test_invalidUser() {
		UserData user = new UserData();
		Errors error = new BeanPropertyBindingResult(user, "userData");

		testClass.validate(user, error);
		assertTrue(error.hasErrors());
	}
	
	@Test
	void test_validUser() {
		UserData user = new UserData();
		user.setUserId("userId");
		user.setName("Zheng");
		Errors error = new BeanPropertyBindingResult(user, "userData");

		testClass.validate(user, error);
		assertFalse(error.hasErrors());
	}
}
  • Line 11: create an instance of the UserDataValidator class.
  • Line 18, 29: validate the userData object.

4. User RestController

In this step, I will create a UserRestController class that utilizes the UserDataValidator defined in step 3.2 to validate the user request when creating a user.

UserReestController.java

package org.zheng.demo.validator.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.zheng.demo.validator.model.UserData;
import org.zheng.demo.validator.model.UserDataValidator;

@RestController
@RequestMapping("/api")
public class UserRestController {
	@Autowired 
	private UserDataValidator ud;

	@GetMapping("/ping")
	public String ping() {
		return "ok";
	}

	@PostMapping("/user")
	public ResponseEntity<?> createUser(@RequestBody UserData user) {
		Errors errors = new BeanPropertyBindingResult(user, "userData");
		ud.validate(user, errors);
		if (errors.hasErrors()) {
			return ResponseEntity.badRequest().body(errors.getAllErrors());
		}
		//continue with other tasks, for now, just return for demo
		return new ResponseEntity<>(user, HttpStatus.OK);
	}
}
  • Line 20: injects a UserDataValidator instance.
  • Line 30: invokes the validate method to validate the create request user data.

4.1 UserRestControllerTest

In this step, I will create a UserRestControllerTest and test via @SpringBootTest and @AutoConfigureMockMvc.

UserRestControllerTest.java

package org.zheng.demo.validator.controller;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import org.zheng.demo.validator.model.UserData;

import com.fasterxml.jackson.databind.ObjectMapper;

@SpringBootTest
@AutoConfigureMockMvc
class UserRestControllerTest {

	@Autowired
	private MockMvc mockMvc;

	@Autowired
	private ObjectMapper ob;

	@Test
	void test_ping() throws Exception {
		mockMvc.perform(get("/api/ping")).andExpect(status().isOk()).andExpect(content().string("ok"));
	}

	@Test
	void test_create_invalid() throws Exception {
		UserData user = new UserData();

		mockMvc.perform(post("/api/user").contentType("application/json").content(ob.writeValueAsString(user)))
				.andExpect(status().isBadRequest());
	}

	@Test
	void test_create_invalid_missingName() throws Exception {
		UserData user = new UserData();
		user.setUserId("test");
		mockMvc.perform(post("/api/user").contentType("application/json").content(ob.writeValueAsString(user)))
				.andExpect(status().isBadRequest());
	}


	@Test
	void test_create_ok() throws Exception {
		UserData user = new UserData();
		user.setUserId("test");
		user.setName("testName");
		mockMvc.perform(post("/api/user").contentType("application/json").content(ob.writeValueAsString(user)))
				.andExpect(status().isOk());
	}
}
  • Line 36: validate the create request with an empty userId and name will receive the bad_request code.
  • Line 44: validate the create request with a missing name will receive the bad_request code.
  • Line 54: validate the create request with both userId and name will receive the ok status.

5. Demo

5.1 Junit Tests

Execute the junit test and capture the test results.

Spring Validator Interface Test Results
Figure 1 Test Results

5.2 Rest Test

In this step, I will start the spring boot application and confirm the server is up at port 8080 via the server log.

server log

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::                (v3.3.3)

2024-09-14T05:00:35.697-05:00  INFO 23180 --- [spring-validator-demo] [           main] o.z.d.v.SpringValidatorDemoApplication   : Starting SpringValidatorDemoApplication using Java 17.0.11 with PID 23180 (C:\MaryTools\workspace\spring-validator-demo\bin\main started by azpm0 in C:\MaryTools\workspace\spring-validator-demo)
2024-09-14T05:00:35.705-05:00  INFO 23180 --- [spring-validator-demo] [           main] o.z.d.v.SpringValidatorDemoApplication   : No active profile set, falling back to 1 default profile: "default"
2024-09-14T05:00:36.672-05:00  INFO 23180 --- [spring-validator-demo] [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port 8080 (http)
2024-09-14T05:00:36.688-05:00  INFO 23180 --- [spring-validator-demo] [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2024-09-14T05:00:36.688-05:00  INFO 23180 --- [spring-validator-demo] [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.28]
2024-09-14T05:00:36.771-05:00  INFO 23180 --- [spring-validator-demo] [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2024-09-14T05:00:36.776-05:00  INFO 23180 --- [spring-validator-demo] [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1015 ms
2024-09-14T05:00:37.148-05:00  INFO 23180 --- [spring-validator-demo] [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port 8080 (http) with context path '/'
2024-09-14T05:00:37.158-05:00  INFO 23180 --- [spring-validator-demo] [           main] o.z.d.v.SpringValidatorDemoApplication   : Started SpringValidatorDemoApplication in 1.826 seconds (process running for 2.222)

Once the server is started, navigate to a web browser and validate with the “http://localhost:8080/api/ping“. It should return the “ok” string.

In this step, I will use the Thunder Client plugin in Visual Studio Code to test the create user request.

Figure 2 shows the bad request error when the request body is missing.

Rest Validator Test 1
Figure 2 Rest Test Validation for Missing Body

Capture the generated curl command for no body request:

curl command for no body content request

curl  -X POST \
  'http://localhost:8080/api/user' \
  --header 'Accept: */*' \
  --header 'User-Agent: Thunder Client (https://www.thunderclient.com)' \
  --header 'Content-Type: application/json'

Figure 3 shows the missing name validation.

Rest Validation Test 2
Figure 3 Missing Name Request

Capture the generated curl command for missing the name field request:

curl command for missing name

curl  -X POST \
  'http://localhost:8080/api/user' \
  --header 'Accept: */*' \
  --header 'User-Agent: Thunder Client (https://www.thunderclient.com)' \
  --header 'Content-Type: application/json' \
  --data-raw '{"userId":"test"}'

Figure 4 shows a valid request.

Rest Validation Valid Request
Figure 4 Valid Request

Capture the generated curl command for a valid request:

curl command for a valid request

curl  -X POST \
  'http://localhost:8080/api/user' \
  --header 'Accept: */*' \
  --header 'User-Agent: Thunder Client (https://www.thunderclient.com)' \
  --header 'Content-Type: application/json' \
  --data-raw '{
  "userId": "test",
  "name": "zheng"
}'

6. Conclusion

In this example, I created a spring boot application for a Rest controller. The controller utilized the Spring validator interface to validate the request.

7. Download

This was an example of a gradle project which includes the Spring Validator interface.

Download
You can download the full source code of this example here: Spring Validator Validation Example

Mary Zheng

Mary graduated from the Mechanical Engineering department at ShangHai JiaoTong University. She also holds a Master degree in Computer Science from Webster University. During her studies she has been involved with a large number of projects ranging from programming and software engineering. She worked as a lead Software Engineer where she led and worked with others to design, implement, and monitor the software solution.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button