Spring Conversion and Validation

Last update: 2023-11-28 10:33:04 +0800 +0800
Available translations: 日本語 / 中文(简体) / 中文(繁體)

Spring treats conversion, presentation formatting, object binding, and validation as separate operations. A request parameter can therefore become a typed property and still produce structured errors without coupling the domain object to a web controller.

Conversion and Formatting #

ConversionService is the facade for type conversion. It can first answer whether a conversion is available and then convert by raw class or by TypeDescriptor, which preserves annotations and nested generic information.

The extension interfaces cover different requirements:

  • Converter<S, T> implements one strongly typed conversion.
  • ConverterFactory<S, R> creates converters for a family of target subtypes, such as String to any enum type.
  • GenericConverter declares sets of convertible source/target pairs and receives TypeDescriptor values for context-sensitive conversion.
  • ConditionalGenericConverter can also decide whether it matches a particular source and target.
java
final class StringToOrderId implements Converter<String, OrderId> {
    @Override
    public OrderId convert(String source) {
        return new OrderId(UUID.fromString(source.strip()));
    }
}

Formatter<T> belongs to the presentation layer. Its Printer and Parser operations use a Locale, which makes it suitable for numbers, dates, and human-facing text. Use a converter for context-free type changes and a formatter when textual representation depends on locale or UI policy. Register both through FormatterRegistry; DefaultFormattingConversionService implements both registry and conversion-service contracts.

Legacy JavaBeans Property Editors #

Spring still supports java.beans.PropertyEditor for compatibility. PropertyEditor is stateful, works primarily through text and an internal value, and is not thread-safe. The container consequently creates editors per binding operation rather than sharing instances.

The JavaBeans convention looks for an editor named TypeEditor beside Type, and PropertyEditorManager also supports explicit registration and search paths. Spring adds PropertyEditorRegistry for type-wide or property-path registration and PropertyEditorRegistrar for reusable registration logic. BeanWrapper and DataBinder expose those registries.

New application conversion should use ConversionService; an editor remains appropriate when integrating a JavaBeans API that requires one. An HTTP message body is not generally decoded by a property editor: Spring MVC uses HttpMessageConverter for body formats such as JSON.

Binding With DataBinder #

DataBinder applies input property values to a target object. It resolves nested property paths, invokes conversion or property editors, records failures in a BindingResult, and can invoke validators.

java
var binder = new DataBinder(command, "order");
binder.setAllowedFields("customerId", "quantity");
binder.setConversionService(conversionService);
binder.addValidators(new OrderValidator());
binder.bind(propertyValues);
binder.validate();

BindingResult result = binder.getBindingResult();

Binding untrusted input is a security boundary. Define allowed fields or use a dedicated command object rather than expose a broad domain object. A conversion failure becomes a field error instead of requiring an exception to escape the binding operation. Auto-growing nested paths and collections is configurable and should be bounded for attacker-controlled input.

Spring’s Validator Contract #

A Spring Validator declares whether it supports a class and reports failures through Errors:

java
final class OrderValidator implements Validator {
    public boolean supports(Class<?> type) {
        return OrderCommand.class.isAssignableFrom(type);
    }

    public void validate(Object target, Errors errors) {
        var order = (OrderCommand) target;
        ValidationUtils.rejectIfEmpty(errors, "customerId", "required");
        if (order.quantity() <= 0) {
            errors.rejectValue("quantity", "positive");
        }
    }
}

Errors.pushNestedPath() and popNestedPath() let a composite validator report child errors with correct paths. Error codes are logical identifiers; message resolution can later select localized text.

Jakarta Bean Validation #

Spring 6 integrates with jakarta.validation Bean Validation 3.x. LocalValidatorFactoryBean bootstraps a provider such as Hibernate Validator and implements both Jakarta’s ValidatorFactory/Validator access and Spring’s Validator interface. It participates in Spring lifecycle management and closes the underlying factory on shutdown.

MVC binding can apply constraint annotations through @Valid or @Validated. Method validation applies constraints to method parameters and return values through Spring’s method-validation infrastructure. Validation does not replace conversion: values must first be represented in the types on which constraints operate.

Primary References #

Comments

Read or comment on GitHub (sign-in required to comment)