This version is still in development and is not considered stable yet. For the latest stable version, please use Spring Framework 6.0.25!

DataBinder

@Controller or @ControllerAdvice classes can have @InitBinder methods that initialize instances of WebDataBinder, and those, in turn, can:

  • Bind request parameters (that is, form or query data) to a model object.

  • Convert String-based request values (such as request parameters, path variables, headers, cookies, and others) to the target type of controller method arguments.

  • Format model object values as String values when rendering HTML forms.

@InitBinder methods can register controller-specific java.beans.PropertyEditor or Spring Converter and Formatter components. In addition, you can use the MVC config to register Converter and Formatter types in a globally shared FormattingConversionService.

@InitBinder methods support many of the same arguments that @RequestMapping methods do, except for @ModelAttribute (command object) arguments. Typically, they are declared with a WebDataBinder argument (for registrations) and a void return value. The following listing shows an example:

  • Java

  • Kotlin

@Controller
public class FormController {

	@InitBinder (1)
	public void initBinder(WebDataBinder binder) {
		SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
		dateFormat.setLenient(false);
		binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
	}

	// ...
}
1 Defining an @InitBinder method.
@Controller
class FormController {

	@InitBinder (1)
	fun initBinder(binder: WebDataBinder) {
		val dateFormat = SimpleDateFormat("yyyy-MM-dd")
		dateFormat.isLenient = false
		binder.registerCustomEditor(Date::class.java, CustomDateEditor(dateFormat, false))
	}

	// ...
}
1 Defining an @InitBinder method.

Alternatively, when you use a Formatter-based setup through a shared FormattingConversionService, you can re-use the same approach and register controller-specific Formatter implementations, as the following example shows:

  • Java

  • Kotlin

@Controller
public class FormController {

	@InitBinder (1)
	protected void initBinder(WebDataBinder binder) {
		binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
	}

	// ...
}
1 Defining an @InitBinder method on a custom formatter.
@Controller
class FormController {

	@InitBinder (1)
	protected fun initBinder(binder: WebDataBinder) {
		binder.addCustomFormatter(DateFormatter("yyyy-MM-dd"))
	}

	// ...
}
1 Defining an @InitBinder method on a custom formatter.

Model Design

In the context of web applications, data binding involves the binding of HTTP request parameters (that is, form data or query parameters) to properties in a model object and its nested objects.

Only public properties following the JavaBeans naming conventions are exposed for data binding — for example, public String getFirstName() and public void setFirstName(String) methods for a firstName property.

The model object, and its nested object graph, is also sometimes referred to as a command object, form-backing object, or POJO (Plain Old Java Object).

By default, Spring permits binding to all public properties in the model object graph. This means you need to carefully consider what public properties the model has, since a client could target any public property path, even some that are not expected to be targeted for a given use case.

For example, given an HTTP form data endpoint, a malicious client could supply values for properties that exist in the model object graph but are not part of the HTML form presented in the browser. This could lead to data being set on the model object and any of its nested objects, that is not expected to be updated.

The recommended approach is to use a dedicated model object that exposes only properties that are relevant for the form submission. For example, on a form for changing a user’s email address, the model object should declare a minimum set of properties such as in the following ChangeEmailForm.

public class ChangeEmailForm {

	private String oldEmailAddress;
	private String newEmailAddress;

	public void setOldEmailAddress(String oldEmailAddress) {
		this.oldEmailAddress = oldEmailAddress;
	}

	public String getOldEmailAddress() {
		return this.oldEmailAddress;
	}

	public void setNewEmailAddress(String newEmailAddress) {
		this.newEmailAddress = newEmailAddress;
	}

	public String getNewEmailAddress() {
		return this.newEmailAddress;
	}

}

If you cannot or do not want to use a dedicated model object for each data binding use case, you must limit the properties that are allowed for data binding. Ideally, you can achieve this by registering allowed field patterns via the setAllowedFields() method on WebDataBinder.

For example, to register allowed field patterns in your application, you can implement an @InitBinder method in a @Controller or @ControllerAdvice component as shown below:

@Controller
public class ChangeEmailController {

	@InitBinder
	void initBinder(WebDataBinder binder) {
		binder.setAllowedFields("oldEmailAddress", "newEmailAddress");
	}

	// @RequestMapping methods, etc.

}

In addition to registering allowed patterns, it is also possible to register disallowed field patterns via the setDisallowedFields() method in DataBinder and its subclasses. Please note, however, that an "allow list" is safer than a "deny list". Consequently, setAllowedFields() should be favored over setDisallowedFields().

Note that matching against allowed field patterns is case-sensitive; whereas, matching against disallowed field patterns is case-insensitive. In addition, a field matching a disallowed pattern will not be accepted even if it also happens to match a pattern in the allowed list.

It is extremely important to properly configure allowed and disallowed field patterns when exposing your domain model directly for data binding purposes. Otherwise, it is a big security risk.

Furthermore, it is strongly recommended that you do not use types from your domain model such as JPA or Hibernate entities as the model object in data binding scenarios.