How to Validate a Map with Spring Validator
Validating user input is a crucial part of any application, and Spring provides robust validation support using JSR-303 annotations through the Hibernate Validator. However, things get tricky when dealing with dynamic data structures like Map<String, String>. Let us delve into understanding how to use Spring Validator to validate data within a Map structure.
1. Hibernate Validator and Maps
When working with Java applications, Hibernate Validator is the reference implementation of the Jakarta Bean Validation specification. It integrates seamlessly with Spring Framework and allows developers to annotate POJO fields with constraints like @NotNull, @Size, @Email, and more.
However, a common challenge arises when developers attempt to apply these validations to Map structures. The Hibernate Validator does not provide out-of-the-box support for validating keys and values within a Map<K, V>. For instance, suppose we have a Map<String, String> representing metadata or request parameters. If we need to ensure that no key or value is blank, applying standard annotations will not suffice.
This limitation is particularly significant when maps are used in REST request bodies or form submissions, where each key-value pair must adhere to specific rules for the application to function correctly and securely.
This is where a custom Spring Validator comes into play. Unlike the Hibernate Validator annotations, the org.springframework.validation.Validator interface allows for programmatic validation logic, which can be applied to complex structures like Map.
2. Code Example
In this section, we will walk through a complete working example of how to validate a Map<String, String> using a custom Spring Validator. The goal is to ensure that neither keys nor values in the map are null or blank.
2.1 Project Configuration
Let’s start by setting up a Spring Boot project using Spring Initializr. Make sure to include the following dependencies in your pom.xml to enable web and validation support:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
The spring-boot-starter-web brings in Spring MVC and related components, while spring-boot-starter-validation adds support for JSR-380 validation APIs like @Valid and @Validated.
2.2 Implementing the Custom Validator
We’ll define a DTO class that contains a Map, and a custom validator that checks whether the map and its entries are valid.
2.2.1 Request DTO
This is our input payload which contains the settings map:
// SettingsRequest.java
public class SettingsRequest {
private Map<String, String> settings;
public Map<String, String> getSettings() {
return settings;
}
public void setSettings(Map<String, String> settings) {
this.settings = settings;
}
}
2.2.2 Custom Validator
This validator ensures that the map is not empty, keys are not blank, and values are not blank. You can enhance it further to include custom business rules.
// SettingsValidator.java
@Component
public class SettingsValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return SettingsRequest.class.equals(clazz);
}
@Override
public void validate(Object target, Errors errors) {
SettingsRequest request = (SettingsRequest) target;
Map<String, String> settings = request.getSettings();
if (settings == null || settings.isEmpty()) {
errors.rejectValue("settings", "settings.empty", "Settings map cannot be empty");
return;
}
for (Map.Entry<String, String> entry : settings.entrySet()) {
if (entry.getKey() == null || entry.getKey().trim().isEmpty()) {
errors.rejectValue("settings", "settings.invalidKey", "Key cannot be blank");
}
if (entry.getValue() == null || entry.getValue().trim().isEmpty()) {
errors.rejectValue("settings", "settings.invalidValue",
"Value for key '" + entry.getKey() + "' cannot be blank");
}
}
}
}
2.3 Calling the Validator
To hook the validator into the Spring MVC validation mechanism, use the @InitBinder annotation inside your controller:
// SettingsController.java
@RestController
@RequestMapping("/api/settings")
public class SettingsController {
private final SettingsValidator settingsValidator;
public SettingsController(SettingsValidator settingsValidator) {
this.settingsValidator = settingsValidator;
}
@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addValidators(settingsValidator);
}
@PostMapping
public ResponseEntity<?> updateSettings(@RequestBody @Valid SettingsRequest request, BindingResult result) {
if (result.hasErrors()) {
List<String> errors = result.getAllErrors().stream()
.map(ObjectError::getDefaultMessage)
.collect(Collectors.toList());
return ResponseEntity.badRequest().body(errors);
}
// Proceed with processing
return ResponseEntity.ok("Settings saved successfully");
}
}
This approach enables seamless integration of custom logic with Spring’s validation pipeline, letting you control how complex structures like maps are verified.
2.4 Code Run
You can test the endpoint using cURL or Postman. The below command sends a sample payload with a blank key and blank value:
curl -X POST http://localhost:8080/api/settings \
-H "Content-Type: application/json" \
-d '{
"settings": {
"theme": "dark",
"": "missingKey",
"language": ""
}
}'
The output will be:
[ "Key cannot be blank", "Value for key 'language' cannot be blank" ]
As shown, the validator correctly captures issues in both keys and values, giving precise feedback to the client.
3. Conclusion
Validating dynamic data structures like Map<String, String> requires custom logic since standard bean validation cannot introspect into map entries. By writing a custom Spring Validator, you gain full control over how keys and values are validated. This pattern is useful when accepting form data, configurations, or JSON maps from the frontend.




