
Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:
Once the early-adopter seats are all used, the price will go up and stay at $33/year.
Last updated: May 9, 2025
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.
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.
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.
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.
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.
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:
Note that we explicitly check the type of keys and values to ensure type safety and avoid ClassCastException at runtime.
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.
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.
As always, all the code examples used in this article and even more examples are available over on GitHub.
Follow the Spring Category