For the latest stable version, please use Spring Framework 6.2.1! |
@ModelAttribute
You can use the @ModelAttribute
annotation on a method argument to access an attribute from
the model or have it be instantiated if not present. The model attribute is also overlain with
values from HTTP Servlet request parameters whose names match to field names. This is referred
to as data binding, and it saves you from having to deal with parsing and converting individual
query parameters and form fields. The following example shows how to do so:
-
Java
-
Kotlin
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute Pet pet) { (1)
// method logic...
}
1 | Bind an instance of Pet . |
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
fun processSubmit(@ModelAttribute pet: Pet): String { (1)
// method logic...
}
1 | Bind an instance of Pet . |
The Pet
instance above is sourced in one of the following ways:
-
Retrieved from the model where it may have been added by a @ModelAttribute method.
-
Retrieved from the HTTP session if the model attribute was listed in the class-level
@SessionAttributes
annotation. -
Obtained through a
Converter
where the model attribute name matches the name of a request value such as a path variable or a request parameter (see next example). -
Instantiated using its default constructor.
-
Instantiated through a “primary constructor” with arguments that match to Servlet request parameters. Argument names are determined through JavaBeans
@ConstructorProperties
or through runtime-retained parameter names in the bytecode.
One alternative to using a @ModelAttribute method to
supply it or relying on the framework to create the model attribute, is to have a
Converter<String, T>
to provide the instance. This is applied when the model attribute
name matches to the name of a request value such as a path variable or a request
parameter, and there is a Converter
from String
to the model attribute type.
In the following example, the model attribute name is account
which matches the URI
path variable account
, and there is a registered Converter<String, Account>
which
could load the Account
from a data store:
-
Java
-
Kotlin
@PutMapping("/accounts/{account}")
public String save(@ModelAttribute("account") Account account) { (1)
// ...
}
1 | Bind an instance of Account using an explicit attribute name. |
@PutMapping("/accounts/{account}")
fun save(@ModelAttribute("account") account: Account): String { (1)
// ...
}
1 | Bind an instance of Account using an explicit attribute name. |
After the model attribute instance is obtained, data binding is applied. The
WebDataBinder
class matches Servlet request parameter names (query parameters and form
fields) to field names on the target Object
. Matching fields are populated after type
conversion is applied, where necessary. For more on data binding (and validation), see
Validation. For more on customizing data binding, see
DataBinder
.
Data binding can result in errors. By default, a BindException
is raised. However, to check
for such errors in the controller method, you can add a BindingResult
argument immediately next
to the @ModelAttribute
, as the following example shows:
-
Java
-
Kotlin
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) { (1)
if (result.hasErrors()) {
return "petForm";
}
// ...
}
1 | Adding a BindingResult next to the @ModelAttribute . |
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
fun processSubmit(@ModelAttribute("pet") pet: Pet, result: BindingResult): String { (1)
if (result.hasErrors()) {
return "petForm"
}
// ...
}
1 | Adding a BindingResult next to the @ModelAttribute . |
In some cases, you may want access to a model attribute without data binding. For such
cases, you can inject the Model
into the controller and access it directly or,
alternatively, set @ModelAttribute(binding=false)
, as the following example shows:
-
Java
-
Kotlin
@ModelAttribute
public AccountForm setUpForm() {
return new AccountForm();
}
@ModelAttribute
public Account findAccount(@PathVariable String accountId) {
return accountRepository.findOne(accountId);
}
@PostMapping("update")
public String update(@Valid AccountForm form, BindingResult result,
@ModelAttribute(binding=false) Account account) { (1)
// ...
}
1 | Setting @ModelAttribute(binding=false) . |
@ModelAttribute
fun setUpForm(): AccountForm {
return AccountForm()
}
@ModelAttribute
fun findAccount(@PathVariable accountId: String): Account {
return accountRepository.findOne(accountId)
}
@PostMapping("update")
fun update(@Valid form: AccountForm, result: BindingResult,
@ModelAttribute(binding = false) account: Account): String { (1)
// ...
}
1 | Setting @ModelAttribute(binding=false) . |
You can automatically apply validation after data binding by adding the
jakarta.validation.Valid
annotation or Spring’s @Validated
annotation
(Bean Validation and
Spring validation). The following example shows how to do so:
-
Java
-
Kotlin
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) { (1)
if (result.hasErrors()) {
return "petForm";
}
// ...
}
1 | Validate the Pet instance. |
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
fun processSubmit(@Valid @ModelAttribute("pet") pet: Pet, result: BindingResult): String { (1)
if (result.hasErrors()) {
return "petForm"
}
// ...
}
1 | Validate the Pet instance. |
Note that using @ModelAttribute
is optional (for example, to set its attributes).
By default, any argument that is not a simple value type (as determined by
BeanUtils#isSimpleProperty)
and is not resolved by any other argument resolver is treated as if it were annotated
with @ModelAttribute
.
When compiling to a native image with GraalVM, the implicit @ModelAttribute
support described above does not allow proper ahead-of-time inference of related data
binding reflection hints. As a consequence, it is recommended to explicitly annotate
method parameters with @ModelAttribute for use in a GraalVM native image.
|