Let's get started with a Microservice Architecture with Spring Cloud:
Validate Map Using Spring Validator
Last updated: May 9, 2025
1. Introduction
Spring’s validation framework is primarily designed to work with JavaBeans, where each field can be annotated with validation constraints.
In this tutorial, we’ll explore how to validate a Map<String, String> using Spring’s Validator interface. This approach is particularly useful when dealing with dynamic key-value pairs that don’t map directly to a predefined Java object.
2. Understand the Problem – Hibernate Validator and Maps
Before implementing a custom validator, it’s natural to try and apply standard constraint annotations directly on the map structure using Hibernate Validator and Spring’s built-in validation mechanisms like @Valid and @Validated. Unfortunately, this approach doesn’t work as we might expect.
Let’s look at an example:
Map<@Length(min = 10) String, @NotBlank String> givenMap = new HashMap<>();
givenMap.put("tooShort", "");
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
Set<ConstraintViolation<Map<String, String>>> violations = validator.validate(givenMap);
Assertions.assertThat(violations).isNotEmpty(); // this will fall
Despite the annotated type parameters, the violations set will be empty — no constraint violations will be detected.
2.1. Why This Fails?
Hibernate Validator, or Bean Validation in general, operates based on JavaBeans conventions, meaning it validates object properties accessible via getters. Since maps don’t expose keys and values as properties, constraint annotations like @Length or @NotBlank aren’t directly applicable and are ignored during validation.
In other words, from the validator’s perspective, a map is a black box — it doesn’t know how to introspect its contents unless explicitly told how.
2.2. When Does It Work?
Type-level constraint annotations can work when a map is a property within a JavaBean, like so:
public class WrappedMap {
private Map<@Length(min = 10) String, @NotBlank String> map;
// constructor, getters, setters...
}
This works thanks to support for container element constraints in Hibernate Validator. However, validation of both keys and values is still limited and inconsistent, especially for maps. You may need to explicitly enable value extractors, and even then, full support isn’t guaranteed.
To address this limitation, we can create a custom validator by implementing the Validator interface provided by Spring.
3. Project Configuration
Before implementing our solution, we need to configure the project with the necessary dependencies. If we’re using Spring Boot, everything is included in a single starter:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>3.4.5</version>
</dependency>
However, if we’re working with plain Spring Framework, you’ll need to include the Jakarta Validation API and its Hibernate implementation manually:
<!-- Core Spring Framework -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.2.6</version>
</dependency>
<!-- Validation related -->
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>3.1.1</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>8.0.2.Final</version>
</dependency>
These dependencies enable us to implement and use a custom Validator for our map structure.
4. Implementing the Custom Validator
Once the project is set up, we can proceed to implement the custom validator. We’ll replicate the validation rules from the earlier code snippet, checking both the keys and the values:
@Service
public class MapValidator implements Validator {
@Override
public boolean supports(Class<?> clazz) {
return Map.class.equals(clazz);
}
@Override
public void validate(Object target, Errors errors) {
Map<?, ?> rawMap = (Map<?, ?>) target;
for (Map.Entry<?, ?> entry : rawMap.entrySet()) {
Object rawKey = entry.getKey();
Object rawValue = entry.getValue();
if (!(rawKey instanceof String key) || !(rawValue instanceof String value)) {
errors.rejectValue("map[" + rawKey + "]", "map.entry.invalidType", "Map must contain only String keys and values");
continue;
}
// Key validation
if (key.length() < 10) {
errors.rejectValue("map[" + key + "]", "key.tooShort", "Key must be at least 10 characters long");
}
// Value validation
if (!StringUtils.hasText(value)) {
errors.rejectValue("map[" + key + "]", "value.blank", "Value must not be blank");
}
}
}
}
This class implements Spring’s Validator interface, which requires two methods:
- supports(Class<?> clazz) – determines whether this validator can handle the given class
- validate(Object target, Errors errors) – performs the actual validation and reports any constraint violations
Note that we explicitly check the type of keys and values to ensure type safety and avoid ClassCastException at runtime.
5. Calling the Validator
Spring’s validation framework integrates smoothly with service classes, allowing us to create reusable code, inject it, and use our custom validator wherever it’s needed.
We can now inject and use your custom validator inside any Spring-managed service:
@Service
public class MapService {
private final MapValidator mapValidator;
@Autowired
public MapService(MapValidator mapValidator) {
this.mapValidator = mapValidator;
}
public void process(Map<String, String> inputMap) {
// Wrap the map in a binding structure for validation
MapBindingResult errors = new MapBindingResult(inputMap, "inputMap");
// Run validation
mapValidator.validate(inputMap, errors);
// Handle validation errors
if (errors.hasErrors()) {
throw new IllegalArgumentException("Validation failed: " + errors.getAllErrors());
}
// Business logic goes here...
}
}
This example shows how the MapValidator can be injected using constructor injection and invoked before executing core business logic. Wrapping the map in a MapBindingResult allows Spring to collect and structure validation errors consistently.
6. Conclusion
Validating Map<String, String> structures in Spring requires a custom approach, as standard validation mechanisms don’t introspect map contents by default. Support for Bean Validation is limited and might not work as we expect.
By implementing the Validator interface and integrating it into your service layer, we gain full control over how each key-value pair is validated, making our application both more robust and more flexible. This strategy is especially helpful when dealing with dynamic inputs like configurations, user-defined forms, or third-party JSON structures.
The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.
















