@ModelAttribute

Using @ModelAttribute on a method

The @ModelAttribute annotation can be used on methods or on method arguments. This section explains its usage on methods while the next section explains its usage on method arguments.

An @ModelAttribute on a method indicates the purpose of that method is to add one or more model attributes. Such methods support the same argument types as @RequestMapping methods but cannot be mapped directly to requests. Instead @ModelAttribute methods in a controller are invoked before @RequestMapping methods, within the same controller. A couple of examples:

// Add one attribute
// The return value of the method is added to the model under the name "account"
// You can customize the name via @ModelAttribute("myAccount")

@ModelAttribute
public Account addAccount(@RequestParam String number) {
	return accountManager.findAccount(number);
}

// Add multiple attributes

@ModelAttribute
public void populateModel(@RequestParam String number, Model model) {
	model.addAttribute(accountManager.findAccount(number));
	// add more ...
}

@ModelAttribute methods are used to populate the model with commonly needed attributes for example to fill a drop-down with states or with pet types, or to retrieve a command object like Account in order to use it to represent the data on an HTML form. The latter case is further discussed in the next section.

Note the two styles of @ModelAttribute methods. In the first, the method adds an attribute implicitly by returning it. In the second, the method accepts a Model and adds any number of model attributes to it. You can choose between the two styles depending on your needs.

A controller can have any number of @ModelAttribute methods. All such methods are invoked before @RequestMapping methods of the same controller.

@ModelAttribute methods can also be defined in an @ControllerAdvice-annotated class and such methods apply to many controllers. See the [mvc-ann-controller-advice] section for more details.

Tip

What happens when a model attribute name is not explicitly specified? In such cases a default name is assigned to the model attribute based on its type. For example if the method returns an object of type Account, the default name used is "account". You can change that through the value of the @ModelAttribute annotation. If adding attributes directly to the Model, use the appropriate overloaded addAttribute(..) method - i.e., with or without an attribute name.

The @ModelAttribute annotation can be used on @RequestMapping methods as well. In that case the return value of the @RequestMapping method is interpreted as a model attribute rather than as a view name. The view name is then derived based on view name conventions instead, much like for methods returning void.

Using @ModelAttribute on a method argument

As explained in the previous section @ModelAttribute can be used on methods or on method arguments. This section explains its usage on method arguments.

An @ModelAttribute on a method argument indicates the argument should be retrieved from the model. If not present in the model, the argument should be instantiated first and then added to the model. Once present in the model, the argument’s fields should be populated from all request parameters that have matching names. This is known as data binding in Spring MVC, a very useful mechanism that saves you from having to parse each form field individually.

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute Pet pet) { }

Given the above example where can the Pet instance come from? There are several options:

  • It may already be in the model due to use of @SessionAttributes — see [mvc-ann-sessionattrib].

  • It may already be in the model due to an @ModelAttribute method in the same controller — as explained in the previous section.

  • It may be retrieved based on a URI template variable and type converter (explained in more detail below).

  • It may be instantiated using its default constructor.

An @ModelAttribute method is a common way to retrieve an attribute from the database, which may optionally be stored between requests through the use of @SessionAttributes. In some cases it may be convenient to retrieve the attribute by using an URI template variable and a type converter. Here is an example:

@PutMapping("/accounts/{account}")
public String save(@ModelAttribute("account") Account account) {
	// ...
}

In this example the name of the model attribute (i.e. "account") matches the name of a URI template variable. If you register Converter<String, Account> that can turn the String account value into an Account instance, then the above example will work without the need for an @ModelAttribute method.

The next step is data binding. The WebDataBinder class matches request parameter names — including query string parameters and form fields — to model attribute fields by name. Matching fields are populated after type conversion (from String to the target field type) has been applied where necessary. Data binding and validation are covered in [validation]. Customizing the data binding process for a controller level is covered in [mvc-ann-webdatabinder].

As a result of data binding there may be errors such as missing required fields or type conversion errors. To check for such errors add a BindingResult argument immediately following the @ModelAttribute argument:

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) {

	if (result.hasErrors()) {
		return "petForm";
	}

	// ...

}

With a BindingResult you can check if errors were found in which case it’s common to render the same form where the errors can be shown with the help of Spring’s <errors> form tag.

Note that in some cases it may be useful to gain access to an attribute in the model without data binding. For such cases you may inject the Model into the controller or alternatively use the binding flag on the annotation:

@ModelAttribute
public AccountForm setUpForm() {
    return new AccountForm();
}

@ModelAttribute
public Account findAccount(@PathVariable String accountId) {
    return accountRepository.findOne(accountId);
}

@PostMapping("update")
public String update(@Valid AccountUpdateForm form, BindingResult result,
        @ModelAttribute(binding=false) Account account) {

    // ...
}

In addition to data binding you can also invoke validation using your own custom validator passing the same BindingResult that was used to record data binding errors. That allows for data binding and validation errors to be accumulated in one place and subsequently reported back to the user:

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) {

	new PetValidator().validate(pet, result);
	if (result.hasErrors()) {
		return "petForm";
	}

	// ...

}

Or you can have validation invoked automatically by adding the JSR-303 @Valid annotation:

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) {

	if (result.hasErrors()) {
		return "petForm";
	}

	// ...

}

results matching ""

    No results matching ""