Java Bean Validation

The Spring Framework provides support for the Java Bean Validation API.

Overview of Bean Validation

Bean Validation provides a common way of validation through constraint declaration and metadata for Java applications. To use it, you annotate domain model properties with declarative validation constraints which are then enforced by the runtime. There are built-in constraints, and you can also define your own custom constraints.

Consider the following example, which shows a simple PersonForm model with two properties:

  • Java

  • Kotlin

public class PersonForm {
	private String name;
	private int age;
}
class PersonForm(
		private val name: String,
		private val age: Int
)

Bean Validation lets you declare constraints as the following example shows:

  • Java

  • Kotlin

public class PersonForm {

	@NotNull
	@Size(max=64)
	private String name;

	@Min(0)
	private int age;
}
class PersonForm(
	@get:NotNull @get:Size(max=64)
	private val name: String,
	@get:Min(0)
	private val age: Int
)

A Bean Validation validator then validates instances of this class based on the declared constraints. See Bean Validation for general information about the API. See the Hibernate Validator documentation for specific constraints. To learn how to set up a bean validation provider as a Spring bean, keep reading.

Configuring a Bean Validation Provider

Spring provides full support for the Bean Validation API including the bootstrapping of a Bean Validation provider as a Spring bean. This lets you inject a jakarta.validation.ValidatorFactory or jakarta.validation.Validator wherever validation is needed in your application.

You can use the LocalValidatorFactoryBean to configure a default Validator as a Spring bean, as the following example shows:

  • Java

  • XML

import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;

@Configuration
public class AppConfig {

	@Bean
	public LocalValidatorFactoryBean validator() {
		return new LocalValidatorFactoryBean();
	}
}
<bean id="validator"
	class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>

The basic configuration in the preceding example triggers bean validation to initialize by using its default bootstrap mechanism. A Bean Validation provider, such as the Hibernate Validator, is expected to be present in the classpath and is automatically detected.

Inject Jakarta Validator

LocalValidatorFactoryBean implements both jakarta.validation.ValidatorFactory and jakarta.validation.Validator, so you can inject a reference to the latter to apply validation logic if you prefer to work with the Bean Validation API directly, as the following example shows:

  • Java

  • Kotlin

import jakarta.validation.Validator;

@Service
public class MyService {

	@Autowired
	private Validator validator;
}
import jakarta.validation.Validator;

@Service
class MyService(@Autowired private val validator: Validator)

Inject Spring Validator

In addition to implementing jakarta.validation.Validator, LocalValidatorFactoryBean also adapts to org.springframework.validation.Validator, so you can inject a reference to the latter if your bean requires the Spring Validation API.

For example:

  • Java

  • Kotlin

import org.springframework.validation.Validator;

@Service
public class MyService {

	@Autowired
	private Validator validator;
}
import org.springframework.validation.Validator

@Service
class MyService(@Autowired private val validator: Validator)

When used as org.springframework.validation.Validator, LocalValidatorFactoryBean invokes the underlying jakarta.validation.Validator, and then adapts ContraintViolations to FieldErrors, and registers them with the Errors object passed into the validate method.

Configure Custom Constraints

Each bean validation constraint consists of two parts:

  • A @Constraint annotation that declares the constraint and its configurable properties.

  • An implementation of the jakarta.validation.ConstraintValidator interface that implements the constraint’s behavior.

To associate a declaration with an implementation, each @Constraint annotation references a corresponding ConstraintValidator implementation class. At runtime, a ConstraintValidatorFactory instantiates the referenced implementation when the constraint annotation is encountered in your domain model.

By default, the LocalValidatorFactoryBean configures a SpringConstraintValidatorFactory that uses Spring to create ConstraintValidator instances. This lets your custom ConstraintValidators benefit from dependency injection like any other Spring bean.

The following example shows a custom @Constraint declaration followed by an associated ConstraintValidator implementation that uses Spring for dependency injection:

  • Java

  • Kotlin

@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy=MyConstraintValidator.class)
public @interface MyConstraint {
}
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.FIELD)
@Retention(AnnotationRetention.RUNTIME)
@Constraint(validatedBy = MyConstraintValidator::class)
annotation class MyConstraint
  • Java

  • Kotlin

import jakarta.validation.ConstraintValidator;

public class MyConstraintValidator implements ConstraintValidator {

	@Autowired;
	private Foo aDependency;

	// ...
}
import jakarta.validation.ConstraintValidator

class MyConstraintValidator(private val aDependency: Foo) : ConstraintValidator {

	// ...
}

As the preceding example shows, a ConstraintValidator implementation can have its dependencies @Autowired as any other Spring bean.

Spring-driven Method Validation

You can integrate the method validation feature of Bean Validation into a Spring context through a MethodValidationPostProcessor bean definition:

  • Java

  • XML

import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;

@Configuration
public class AppConfig {

	@Bean
	public MethodValidationPostProcessor validationPostProcessor() {
		return new MethodValidationPostProcessor();
	}
}
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/>

To be eligible for Spring-driven method validation, target classes need to be annotated with Spring’s @Validated annotation, which can optionally also declare the validation groups to use. See MethodValidationPostProcessor for setup details with the Hibernate Validator and Bean Validation providers.

Method validation relies on AOP Proxies around the target classes, either JDK dynamic proxies for methods on interfaces or CGLIB proxies. There are certain limitations with the use of proxies, some of which are described in Understanding AOP Proxies. In addition remember to always use methods and accessors on proxied classes; direct field access will not work.

Spring MVC and WebFlux have built-in support for the same underlying method validation but without the need for AOP. Therefore, do check the rest of this section, and also see the Spring MVC Validation and Error Responses sections, and the WebFlux Validation and Error Responses sections.

Method Validation Exceptions

By default, jakarta.validation.ConstraintViolationException is raised with the set of ConstraintViolations returned by jakarata.validation.Validator. As an alternative, you can have MethodValidationException raised instead with ConstraintViolations adapted to MessageSourceResolvable errors. To enable set the following flag:

  • Java

  • XML

import org.springframework.validation.beanvalidation.MethodValidationPostProcessor;

@Configuration
public class AppConfig {

	@Bean
	public MethodValidationPostProcessor validationPostProcessor() {
		MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
		processor.setAdaptConstraintViolations(true);
		return processor;
	}
}
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor">
	<property name="adaptConstraintViolations" value="true"/>
</bean>

MethodValidationException contains a list of ParameterValidationResults which group errors by method parameter, and each exposes a MethodParameter, the argument value, and a list of MessageSourceResolvable errors adapted from ConstraintViolations. For @Valid method parameters with cascaded violations on fields and properties, the ParameterValidationResult is ParameterErrors which implements org.springframework.validation.Errors and exposes validation errors as FieldErrors.

Customizing Validation Errors

The adapted MessageSourceResolvable errors can be turned into error messages to display to users through the configured MessageSource with locale and language specific resource bundles. This section provides an example for illustration.

Given the following class declarations:

  • Java

  • Kotlin

record Person(@Size(min = 1, max = 10) String name) {
}

@Validated
public class MyService {

	void addStudent(@Valid Person person, @Max(2) int degrees) {
		// ...
	}
}
@JvmRecord
internal data class Person(@Size(min = 1, max = 10) val name: String)

@Validated
class MyService {

	fun addStudent(person: @Valid Person?, degrees: @Max(2) Int) {
		// ...
	}
}

A ConstraintViolation on Person.name() is adapted to a FieldError with the following:

  • Error codes "Size.student.name", "Size.name", "Size.java.lang.String", and "Size"

  • Message arguments "name", 10, and 1 (the field name and the constraint attributes)

  • Default message "size must be between 1 and 10"

To customize the default message, you can add properties to MessageSource resource bundles using any of the above errors codes and message arguments. Note also that the message argument "name" is itself a MessagreSourceResolvable with error codes "student.name" and "name" and can customized too. For example:

Properties
Size.student.name=Please, provide a {0} that is between {2} and {1} characters long
student.name=username

A ConstraintViolation on the degrees method parameter is adapted to a MessageSourceResolvable with the following:

  • Error codes "Max.myService#addStudent.degrees", "Max.degrees", "Max.int", "Max"

  • Message arguments "degrees2 and 2 (the field name and the constraint attribute)

  • Default message "must be less than or equal to 2"

To customize the above default message, you can add a property such as:

Properties
Max.degrees=You cannot provide more than {1} {0}

Additional Configuration Options

The default LocalValidatorFactoryBean configuration suffices for most cases. There are a number of configuration options for various Bean Validation constructs, from message interpolation to traversal resolution. See the LocalValidatorFactoryBean javadoc for more information on these options.

Configuring a DataBinder

You can configure a DataBinder instance with a Validator. Once configured, you can invoke the Validator by calling binder.validate(). Any validation Errors are automatically added to the binder’s BindingResult.

The following example shows how to use a DataBinder programmatically to invoke validation logic after binding to a target object:

  • Java

  • Kotlin

Foo target = new Foo();
DataBinder binder = new DataBinder(target);
binder.setValidator(new FooValidator());

// bind to the target object
binder.bind(propertyValues);

// validate the target object
binder.validate();

// get BindingResult that includes any validation errors
BindingResult results = binder.getBindingResult();
val target = Foo()
val binder = DataBinder(target)
binder.validator = FooValidator()

// bind to the target object
binder.bind(propertyValues)

// validate the target object
binder.validate()

// get BindingResult that includes any validation errors
val results = binder.bindingResult

You can also configure a DataBinder with multiple Validator instances through dataBinder.addValidators and dataBinder.replaceValidators. This is useful when combining globally configured bean validation with a Spring Validator configured locally on a DataBinder instance. See Spring MVC Validation Configuration.

Spring MVC 3 Validation

See Validation in the Spring MVC chapter.