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 thevalidator
can validate instances of the given class. In this example, it supports the validation at theUserData
class. - Line 17: the
validate
method performs the actual validation on the giventarget
object and records any validation errors in the givenerrors
object. In this example, it utilizes theValidationUtils.rejectIfEmptyOrWhitespace
to validate theuserId
andname
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
andname
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
andname
will receive the ok status.
5. Demo
5.1 Junit Tests
Execute the junit test and capture the 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.
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.
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.
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.
You can download the full source code of this example here: Spring Validator Validation Example