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

@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.