For the latest stable version, please use Spring Framework 6.2.0!

@ModelAttribute

The @ModelAttribute method parameter annotation binds request parameters onto a model object. For example:

  • Java

  • Kotlin

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute Pet pet) { } (1)
1 Bind to an instance of Pet.
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
fun processSubmit(@ModelAttribute pet: Pet): String { } (1)
1 Bind to an instance of Pet.

The Pet instance may be:

  • Accessed from the model where it could have been added by a Model.

  • Accessed from the HTTP session if the model attribute was listed in the class-level @SessionAttributes.

  • Instantiated through a default constructor.

  • Instantiated through a “primary constructor” with arguments that match to Servlet request parameters. Argument names are determined through runtime-retained parameter names in the bytecode.

By default, both constructor and property data binding are applied. However, model object design requires careful consideration, and for security reasons it is recommended either to use an object tailored specifically for web binding, or to apply constructor binding only. If property binding must still be used, then allowedFields patterns should be set to limit which properties can be set. For further details on this and example configuration, see model design.

When using constructor binding, you can customize request parameter names through an @BindParam annotation. For example:

  • Java

  • Kotlin

class Account {

    private final String firstName;

	public Account(@BindParam("first-name") String firstName) {
		this.firstName = firstName;
	}
}
class Account(@BindParam("first-name") val firstName: String)
The @BindParam may also be placed on the fields that correspond to constructor parameters. While @BindParam is supported out of the box, you can also use a different annotation by setting a DataBinder.NameResolver on DataBinder

WebFlux, unlike Spring MVC, supports reactive types in the model, e.g. Mono<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 data binding results in errors, by default a WebExchangeBindException is raised, but you can also add a BindingResult argument immediately next to the @ModelAttribute in order to handle such errors in the controller method. For example:

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

To use a BindingResult argument, you must declare the @ModelAttribute argument before it without a reactive type wrapper. If you want to use the reactive, you can handle errors directly through it. For example:

  • 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 ->
				// ...
			}
}

You can automatically apply validation after data binding by adding the jakarta.validation.Valid annotation or Spring’s @Validated annotation (see Bean Validation and Spring validation). For example:

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

If method validation applies because other parameters have @Constraint annotations, then HandlerMethodValidationException would be raised instead. See the section on controller method Validation.

Using @ModelAttribute is optional. By default, any argument that is not a simple value type as determined by BeanUtils#isSimpleProperty AND that is not resolved by any other argument resolver is treated as an implicit @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.