docs
Data binding capabilities for binding request parameters to form objects, type conversion, and validation using JSR-303/JSR-380 Bean Validation along with Spring's validation framework.
Annotation for methods that initialize WebDataBinder instances for data binding customization, including setting allowed/disallowed fields, registering custom editors, and adding validators.
/**
* Annotation that identifies methods that initialize the WebDataBinder
* which will be used for populating command and form object arguments
* of annotated handler methods.
*
* @InitBinder methods support all arguments that @RequestMapping methods support,
* except for command/form objects and corresponding validation result objects.
* @InitBinder methods must not have a return value; they are usually declared as void.
*
* Typical arguments are WebDataBinder in combination with WebRequest or Locale,
* allowing to register context-specific editors.
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface InitBinder {
/**
* The names of command/form attributes and/or request parameters
* that this init-binder method is supposed to apply to.
* Default is to apply to all command/form attributes and all request parameters
* processed by the annotated handler class. Specifying model attribute names or
* request parameter names here restricts the init-binder method to those specific
* attributes/parameters.
*/
String[] value() default {};
}Usage Example:
@RestControllerAdvice
public class DataBindingAdvice {
// Global init binder applying to all controllers
@InitBinder
public void initBinder(WebDataBinder binder) {
// Disallow binding of sensitive fields
binder.setDisallowedFields("id", "createdAt", "updatedAt");
// Custom validators
binder.addValidators(new ProductValidator());
// Custom property editor
binder.registerCustomEditor(Date.class, new CustomDateEditor(
new SimpleDateFormat("yyyy-MM-dd"), true));
}
// Specific init binder only for "user" model attribute
@InitBinder("user")
public void initUserBinder(WebDataBinder binder) {
binder.setDisallowedFields("role", "permissions");
}
// Multiple model attributes
@InitBinder({"product", "inventory"})
public void initProductBinder(WebDataBinder binder) {
binder.setAllowedFields("name", "price", "quantity");
}
}Type-level annotation that indicates which model attributes should be stored in the HTTP session between requests, useful for multi-page forms and conversational workflows.
/**
* Annotation that indicates the session attributes that a specific handler uses.
*
* This will typically list the names of model attributes which should be
* transparently stored in the session or some conversational storage,
* serving as form-backing beans. Declared at the type level, applying
* to the model attributes that the annotated handler class operates on.
*
* Session attributes as indicated using this annotation correspond to a specific
* handler's model attributes, getting transparently stored in a conversational session.
* Those attributes will be removed once the handler indicates completion of its
* conversational session.
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface SessionAttributes {
/**
* Alias for names().
*/
String[] value() default {};
/**
* The names of session attributes in the model that should be stored in the
* session or some conversational storage.
* Note: This indicates the model attribute names. The session attribute names
* may or may not match the model attribute names.
*/
String[] names() default {};
/**
* The types of session attributes in the model that should be stored in the
* session or some conversational storage.
* All model attributes of these types will be stored in the session,
* regardless of attribute name.
*/
Class<?>[] types() default {};
}Usage Example:
@Controller
@RequestMapping("/wizard")
@SessionAttributes({"wizardForm", "userData"})
public class MultiStepFormController {
@GetMapping("/step1")
public String showStep1(Model model) {
// Create form object and add to model
WizardForm form = new WizardForm();
model.addAttribute("wizardForm", form);
return "wizard/step1";
}
@PostMapping("/step1")
public String processStep1(@ModelAttribute("wizardForm") WizardForm form,
BindingResult result) {
// Form is automatically stored in session
// Process step 1 data
form.setStep1Complete(true);
return "redirect:/wizard/step2";
}
@GetMapping("/step2")
public String showStep2(@ModelAttribute("wizardForm") WizardForm form) {
// Form is automatically retrieved from session
if (!form.isStep1Complete()) {
return "redirect:/wizard/step1";
}
return "wizard/step2";
}
@PostMapping("/step2")
public String processStep2(@ModelAttribute("wizardForm") WizardForm form,
BindingResult result,
SessionStatus status) {
// Process final step
form.setStep2Complete(true);
// Save to database
wizardService.save(form);
// Clear session attributes
status.setComplete();
return "redirect:/wizard/complete";
}
}
// Using types instead of names
@Controller
@RequestMapping("/shopping")
@SessionAttributes(types = {ShoppingCart.class, UserPreferences.class})
public class ShoppingController {
// All model attributes of type ShoppingCart or UserPreferences
// will be automatically stored in session
}Variant of JSR-303's @Valid to be used on type level for method validation, or on method parameters to apply validation groups.
/**
* Variant of JSR-303's @Valid, supporting the specification of validation groups.
* Designed for convenient use with Spring's JSR-303 support but not JSR-303 specific.
*
* Can be used for class-level method validation (triggering validation of the arguments
* for any methods with @Valid parameters) or method-level parameter validation.
*/
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Validated {
/**
* Specify one or more validation groups to apply to the validation step
* kicked off by this annotation.
* JSR-303 defines validation groups as custom annotations which an application declares
* for the sole purpose of using them as type-safe group arguments, as implemented in
* SpringValidatorAdapter.
*
* Other SmartValidator implementations may support class arguments in other ways.
*/
Class<?>[] value() default {};
}Usage Example:
@RestController
@RequestMapping("/api/users")
@Validated
public class UserController {
@PostMapping
public ResponseEntity<User> createUser(
@RequestBody @Valid User user) {
User created = userService.save(user);
return ResponseEntity.status(HttpStatus.CREATED).body(created);
}
@PutMapping("/{id}")
public ResponseEntity<User> updateUser(
@PathVariable Long id,
@RequestBody @Validated(UpdateGroup.class) User user) {
User updated = userService.update(id, user);
return ResponseEntity.ok(updated);
}
@PostMapping("/admin")
public ResponseEntity<User> createAdminUser(
@RequestBody @Validated({Default.class, AdminGroup.class}) User user) {
User created = userService.saveAdmin(user);
return ResponseEntity.status(HttpStatus.CREATED).body(created);
}
}
// Domain model with validation groups
public class User {
@NotNull(groups = UpdateGroup.class)
private Long id;
@NotBlank
@Size(min = 3, max = 50)
private String username;
@NotBlank
@Email
private String email;
@NotNull
@Size(min = 8, message = "Password must be at least 8 characters")
private String password;
@NotNull(groups = AdminGroup.class)
private Role role;
}
// Validation groups
public interface UpdateGroup {}
public interface AdminGroup {}Standard JSR-303/JSR-380 annotation for marking a method parameter or field for validation.
/**
* Marks a property, method parameter or method return type for validation cascading.
* Constraints defined on the object and its properties are be validated when the
* property, method parameter or method return type is validated.
*
* This is a standard JSR-303/JSR-380 annotation.
*/
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Valid {
}Interface for data binding errors. Allows storing and evaluating information about data binding errors.
/**
* Stores and exposes information about data-binding and validation
* errors for a specific object.
*
* Field names can be properties of the target object (e.g. "name"
* when binding to a customer object), or nested fields in case of
* subobjects (e.g. "address.street"). Supports subtree navigation
* via setNestedPath(String): e.g. an AddressValidator validates
* "address", not "address.street".
*/
public interface Errors {
/**
* The separator between path elements in a nested path,
* for example in "customer.name" or "customer.address.street".
*/
String NESTED_PATH_SEPARATOR = ".";
/**
* Return the name of the bound root object.
*
* @return the name of the bound object
*/
String getObjectName();
/**
* Set the nested path of this Errors object.
* Can be used to navigate to subobjects before validating.
*
* @param nestedPath nested path within this object
*/
void setNestedPath(String nestedPath) {}
/**
* Return the current nested path of this Errors object.
*
* @return the current nested path
*/
String getNestedPath() {}
/**
* Push the given sub path onto the nested path stack.
*
* @param subPath the sub path to push
*/
void pushNestedPath(String subPath) {}
/**
* Pop the former nested path from the nested path stack.
*/
void popNestedPath() throws IllegalStateException {}
/**
* Register a global error for the entire target object.
*
* @param errorCode error code, interpretable as a message key
*/
void reject(String errorCode) {}
/**
* Register a global error for the entire target object.
*
* @param errorCode error code, interpretable as a message key
* @param defaultMessage fallback default message
*/
void reject(String errorCode, String defaultMessage) {}
/**
* Register a global error for the entire target object.
*
* @param errorCode error code, interpretable as a message key
* @param errorArgs error arguments, for argument binding via MessageFormat
* @param defaultMessage fallback default message
*/
void reject(String errorCode, Object[] errorArgs, String defaultMessage) {}
/**
* Register a field error for the specified field of the current object.
*
* @param field the field name (may be null or empty String)
* @param errorCode error code, interpretable as a message key
*/
void rejectValue(String field, String errorCode) {}
/**
* Register a field error for the specified field of the current object.
*
* @param field the field name (may be null or empty String)
* @param errorCode error code, interpretable as a message key
* @param defaultMessage fallback default message
*/
void rejectValue(String field, String errorCode, String defaultMessage) {}
/**
* Register a field error for the specified field of the current object.
*
* @param field the field name (may be null or empty String)
* @param errorCode error code, interpretable as a message key
* @param errorArgs error arguments, for argument binding via MessageFormat
* @param defaultMessage fallback default message
*/
void rejectValue(String field, String errorCode, Object[] errorArgs, String defaultMessage) {}
/**
* Add all errors from the given Errors instance to this Errors instance.
*
* @param errors the Errors instance to merge in
*/
void addAllErrors(Errors errors) {}
/**
* Return if there were any errors.
*
* @return true if there are any errors
*/
boolean hasErrors() {}
/**
* Return the total number of errors.
*
* @return the total number of errors
*/
int getErrorCount() {}
/**
* Get all errors, both global and field ones.
*
* @return a list of ObjectError instances
*/
List<ObjectError> getAllErrors() {}
/**
* Are there any global errors?
*
* @return true if there are any global errors
*/
boolean hasGlobalErrors() {}
/**
* Return the number of global errors.
*
* @return the number of global errors
*/
int getGlobalErrorCount() {}
/**
* Get all global errors.
*
* @return a list of ObjectError instances
*/
List<ObjectError> getGlobalErrors() {}
/**
* Get the first global error, if any.
*
* @return the global error, or null
*/
ObjectError getGlobalError() {}
/**
* Are there any field errors?
*
* @return true if there are any errors associated with a field
*/
boolean hasFieldErrors() {}
/**
* Return the number of errors associated with a field.
*
* @return the number of errors associated with a field
*/
int getFieldErrorCount() {}
/**
* Get all errors associated with a field.
*
* @return a list of FieldError instances
*/
List<FieldError> getFieldErrors() {}
/**
* Get the first error associated with a field, if any.
*
* @return the field-specific error, or null
*/
FieldError getFieldError() {}
/**
* Are there any errors associated with the given field?
*
* @param field the field name
* @return true if there were any errors associated with the given field
*/
boolean hasFieldErrors(String field) {}
/**
* Return the number of errors associated with the given field.
*
* @param field the field name
* @return the number of errors associated with the given field
*/
int getFieldErrorCount(String field) {}
/**
* Get all errors associated with the given field.
*
* @param field the field name
* @return a list of FieldError instances
*/
List<FieldError> getFieldErrors(String field) {}
/**
* Get the first error associated with the given field, if any.
*
* @param field the field name
* @return the field-specific error, or null
*/
FieldError getFieldError(String field) {}
/**
* Return the current value of the given field, either the current
* bean property value or a rejected update from the last binding.
*
* @param field the field name
* @return the current value of the given field
*/
Object getFieldValue(String field) {}
/**
* Return the type of a given field.
*
* @param field the field name
* @return the type of the field, or null if not determinable
*/
Class<?> getFieldType(String field) {}
}Interface that extends Errors with additional capabilities for registration and evaluation of binding errors.
/**
* General interface that represents binding results. Extends the Errors
* interface for error registration capabilities, allowing for a Validator
* to be applied, and adds binding-specific analysis and model building.
*
* Serves as result holder for a DataBinder, obtained via the DataBinder.getBindingResult()
* method. BindingResult implementations can also be used directly, for example to invoke
* a Validator on it (e.g. as part of a unit test).
*/
public interface BindingResult extends Errors {
/**
* Prefix for the name of the BindingResult instance in a model,
* followed by the object name.
*/
String MODEL_KEY_PREFIX = BindingResult.class.getName() + ".";
/**
* Return the wrapped target object, which may be a bean, an object with
* public fields, a Map - depending on the concrete binding strategy.
*
* @return the target object
*/
Object getTarget() {}
/**
* Return a model Map for the obtained state, exposing a BindingResult
* instance as '{@link #MODEL_KEY_PREFIX MODEL_KEY_PREFIX} + objectName'
* and the object itself as 'objectName'.
*
* @return the model Map
*/
Map<String, Object> getModel() {}
/**
* Extract the raw field value for the given field.
* Typically used for comparison purposes.
*
* @param field the field to check
* @return the current value of the field in its raw form,
* or null if not known
*/
Object getRawFieldValue(String field) {}
/**
* Find a custom property editor for the given type and property.
*
* @param field the path of the property (name or nested path), or
* null if looking for an editor for all properties of the given type
* @param valueType the type of the property (can be null if a property
* is given but should be specified in any case for consistency checking)
* @return the registered editor, or null if none
*/
PropertyEditor findEditor(String field, Class<?> valueType) {}
/**
* Return the underlying PropertyEditorRegistry.
*
* @return the PropertyEditorRegistry, or null if none available
*/
PropertyEditorRegistry getPropertyEditorRegistry() {}
/**
* Resolve the given error code into message codes.
*
* @param errorCode the error code to resolve into message codes
* @return the resolved message codes
*/
String[] resolveMessageCodes(String errorCode) {}
/**
* Resolve the given error code into message codes for the given field.
*
* @param errorCode the error code to resolve into message codes
* @param field the field to resolve message codes for
* @return the resolved message codes
*/
String[] resolveMessageCodes(String errorCode, String field) {}
/**
* Add a custom ObjectError or FieldError to the errors list.
*
* @param error the custom ObjectError or FieldError
*/
void addError(ObjectError error) {}
/**
* Record the given value for the specified field.
*
* @param field the field to record the value for
* @param type the type of the field
* @param value the value to record
*/
default void recordFieldValue(String field, Class<?> type, Object value) {}
/**
* Mark the specified disallowed field as suppressed.
*
* @param field the field to mark as suppressed
*/
default void recordSuppressedField(String field) {}
/**
* Return the list of fields that were suppressed during the bind process.
*
* @return the list of suppressed fields, or an empty array
*/
default String[] getSuppressedFields() {
return new String[0];
}
}Usage Example:
@Controller
@RequestMapping("/products")
public class ProductFormController {
@PostMapping
public String createProduct(@Valid @ModelAttribute("product") Product product,
BindingResult bindingResult,
Model model) {
// Check for validation errors
if (bindingResult.hasErrors()) {
// Add additional model attributes for form
model.addAttribute("categories", categoryService.findAll());
return "product/form";
}
// Custom business validation
if (productService.existsByName(product.getName())) {
bindingResult.rejectValue("name", "duplicate.product.name",
"A product with this name already exists");
model.addAttribute("categories", categoryService.findAll());
return "product/form";
}
productService.save(product);
return "redirect:/products";
}
@PostMapping("/bulk")
public String createProducts(@Valid @ModelAttribute ProductBulkForm form,
BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
// Handle field errors
List<FieldError> fieldErrors = bindingResult.getFieldErrors();
for (FieldError error : fieldErrors) {
System.out.println("Field: " + error.getField() +
", Error: " + error.getDefaultMessage());
}
return "product/bulk-form";
}
productService.saveAll(form.getProducts());
return "redirect:/products";
}
}
// REST controller example with custom error handling
@RestController
@RequestMapping("/api/products")
public class ProductApiController {
@PostMapping
public ResponseEntity<?> createProduct(@RequestBody @Valid Product product,
BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
Map<String, String> errors = new HashMap<>();
bindingResult.getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage())
);
return ResponseEntity.badRequest().body(errors);
}
Product created = productService.save(product);
return ResponseEntity.status(HttpStatus.CREATED).body(created);
}
}Special DataBinder for data binding from web request parameters to JavaBeans.
/**
* Special DataBinder to perform data binding from web request parameters to JavaBeans,
* including support for multipart files.
*
* WARNING: Data binding can lead to security issues by exposing parts of the object graph
* that are not meant to be exposed. It is therefore recommended to design your JavaBeans
* specifically for the purpose of data binding, or to manually initialize the binder with
* allowed/disallowed fields.
*/
public class WebDataBinder extends DataBinder {
/**
* Create a new WebDataBinder instance.
*
* @param target the target object to bind onto (or null if the binder
* is just used to convert a plain parameter value)
*/
public WebDataBinder(Object target) {}
/**
* Create a new WebDataBinder instance.
*
* @param target the target object to bind onto (or null if the binder
* is just used to convert a plain parameter value)
* @param objectName the name of the target object
*/
public WebDataBinder(Object target, String objectName) {}
/**
* Set whether to bind empty MultipartFile parameters.
* Default is "true".
*
* @param bindEmptyMultipartFiles whether to bind empty multipart files
*/
public void setBindEmptyMultipartFiles(boolean bindEmptyMultipartFiles) {}
/**
* Return whether to bind empty MultipartFile parameters.
*
* @return whether to bind empty multipart files
*/
public boolean isBindEmptyMultipartFiles() {}
/**
* Bind the given property values to this binder's target.
*
* @param propertyValues the property values to bind
*/
public void bind(PropertyValues propertyValues) {}
/**
* Close this DataBinder, which may result in throwing a BindException if it
* encountered any errors.
*
* @return the model Map, containing target object and BindingResult
* @throws BindException if there were any errors in the bind operation
*/
public Map<?, ?> close() throws BindException {}
}Annotation for binding values from web request parameters or path variables to constructor parameters or fields when instantiating @ModelAttribute objects.
/**
* Annotation to bind values from a web request such as request parameters or
* path variables to fields of a Java object. Supported on constructor parameters
* of @ModelAttribute controller method arguments.
*
* @since 6.1
*/
@Target({ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface BindParam {
/**
* The lookup name to use for the bind value.
*/
String value() default "";
}Usage Example:
// Domain model with constructor binding
public class Product {
private final Long id;
private final String name;
private final BigDecimal price;
// Constructor-based binding with @BindParam
public Product(
@BindParam("id") Long id,
@BindParam("name") String name,
@BindParam("price") BigDecimal price) {
this.id = id;
this.name = name;
this.price = price;
}
// Getters
public Long getId() { return id; }
public String getName() { return name; }
public BigDecimal getPrice() { return price; }
}
@Controller
@RequestMapping("/products")
public class ProductController {
// Use @ModelAttribute with constructor binding
@PostMapping
public String createProduct(@ModelAttribute Product product) {
// Product is instantiated via constructor with @BindParam
productService.save(product);
return "redirect:/products";
}
@PutMapping("/{id}")
public String updateProduct(
@PathVariable Long id,
@ModelAttribute Product product) {
productService.update(product);
return "redirect:/products/" + id;
}
}
// Field-level @BindParam
public class OrderItem {
@BindParam("product_id")
private Long productId;
@BindParam("quantity")
private int quantity;
@BindParam("unit_price")
private BigDecimal unitPrice;
// No-arg constructor required for field-level binding
public OrderItem() {}
// Getters and setters
}Represents a global error not associated with a specific field.
public class ObjectError {
public ObjectError(String objectName, String defaultMessage) {}
public ObjectError(String objectName, String[] codes, Object[] arguments, String defaultMessage) {}
public String getObjectName() {}
public String[] getCodes() {}
public Object[] getArguments() {}
public String getDefaultMessage() {}
public String getCode() {}
}Represents a field-specific error.
public class FieldError extends ObjectError {
public FieldError(String objectName, String field, String defaultMessage) {}
public FieldError(String objectName, String field, Object rejectedValue,
boolean bindingFailure, String[] codes, Object[] arguments,
String defaultMessage) {}
public String getField() {}
public Object getRejectedValue() {}
public boolean isBindingFailure() {}
}