@ModelAttribute
You can use the @ModelAttribute
annotation on a method argument to access an attribute from the
model or have it instantiated if not present. The model attribute is also overlaid with
the values of query parameters and form fields 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 binds an instance of Pet
:
-
Java
-
Kotlin
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute Pet pet) { } (1)
1 | Bind an instance of Pet . |
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
fun processSubmit(@ModelAttribute pet: Pet): String { } (1)
1 | Bind an instance of Pet . |
The Pet
instance in the preceding example is resolved as follows:
-
From the model if already added through
Model
. -
From the HTTP session through
@SessionAttributes
. -
From the invocation of a default constructor.
-
From the invocation of a “primary constructor” with arguments that match query parameters or form fields. Argument names are determined through JavaBeans
@ConstructorProperties
or through runtime-retained parameter names in the bytecode.
After the model attribute instance is obtained, data binding is applied. The
WebExchangeDataBinder
class matches names of 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 WebExchangeBindException
is raised, but,
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 . |
@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 . |
You can automatically apply validation after data binding by adding the
jakarta.validation.Valid
annotation or Spring’s @Validated
annotation (see also
Bean Validation and
Spring validation). The following example uses the @Valid
annotation:
-
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 | Using @Valid on a model attribute argument. |
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
fun processSubmit(@Valid @ModelAttribute("pet") pet: Pet, result: BindingResult): String { (1)
if (result.hasErrors()) {
return "petForm"
}
// ...
}
1 | Using @Valid on a model attribute argument. |
Spring WebFlux, unlike Spring MVC, supports reactive types in the model — for example,
Mono<Account>
or io.reactivex.Single<Account>
. You can declare a @ModelAttribute
argument
with or without a reactive type wrapper, and it will be resolved accordingly,
to the actual value if necessary. However, note that, to use a BindingResult
argument, you must declare the @ModelAttribute
argument before it without a reactive
type wrapper, as shown earlier. Alternatively, you can handle any errors through the
reactive type, as the following example shows:
-
Java
-
Kotlin
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public Mono<String> processSubmit(@Valid @ModelAttribute("pet") Mono<Pet> petMono) {
return petMono
.flatMap(pet -> {
// ...
})
.onErrorResume(ex -> {
// ...
});
}
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
fun processSubmit(@Valid @ModelAttribute("pet") petMono: Mono<Pet>): Mono<String> {
return petMono
.flatMap { pet ->
// ...
}
.onErrorResume{ ex ->
// ...
}
}
Note that use of @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.
|