Spring WebFlux provides an annotation-based programming model, where the @Controller
and @RestController
components use annotations to express query mappings, query entry, exception handling, and more. Annotated controllers have flexible method signatures and do not necessarily need to extend base classes or implement specific interfaces.
The following listing shows a basic example:
@RestController
public classHelloController {
@GetMapping("/hello") public String handle (){
return "Hello WebFlux";
}
}
@RestController
class HelloController {
@GetMapping("/hello")
fun handle() = "Hello WebFlux"
}
In the previous example, the method returns a String
to write to the response.
@Controller
You can define controller beans using the standard Spring bean definition. The @Controller
stereotype allows for automatic discovery and is consistent with Spring's general support for discovering classes with the @Component
annotation in the classpath and automatically registering bean definitions for them. It also acts as a stereotype for the annotated class, indicating its role as a web component.
To enable automatic detection of such beans with the @Controller
annotation, you can add component scanning to the configuration Java, as shown in the following example:
@Configuration
@ComponentScan("org.example.web")
public class WebConfig {
// ...
}
- Scanning the package
org.example.web
.
@Configuration @ComponentScan("org.example.web")
class WebConfig {
// ...
}
- Scan the package
org.example.web
.
@RestController
is a composite an annotation that is itself meta-annotated with the @Controller
and @ResponseBody
annotations, indicating a controller whose each method inherits the @ResponseBody
annotation at the type level and , therefore, writes directly to the response body instead of recognizing the representation and rendering using an HTML template.
Request Mapping
The @RequestMapping
annotation is used to map requests to controller methods. It has various attributes to match by URL, HTTP method, request parameters, headers, and media types. You can use it at the class level to express general mappings, or at the method level to narrow it down to a specific endpoint mapping.
There are also HTTP method-specific options for shortening the @RequestMapping
annotation:
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@PatchMapping
The previous annotations are custom annotations that are specified because most controller methods will likely need to be mapped to defined by an HTTP method rather than using the @RequestMapping
annotation, which is mapped to all HTTP methods by default. However, the @RequestMapping
annotation is still required at the class level to express general mappings.
The following example uses type and method level mappings:
@RestController
@RequestMapping("/persons") class PersonController {
@GetMapping("/{id}")
public Person getPerson(@PathVariable Long id) {
// ...
}
@PostMapping
@ResponseStatus( HttpStatus.CREATED) public void add(@RequestBody Person person) {
// ...
}
}
@RestController
@RequestMapping("/persons")
class PersonController {
@GetMapping("/{id}") fun getPerson(@PathVariable id: Long): Person {
// ...
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
fun add(@RequestBody person: Person) {
// ...
}
}
URI templates
You can display queries using standard masks (aka search pattern (glob patterns) and wildcards:
Template | Description | Example |
---|---|---|
|
Matches a single character |
|
|
Matches zero or more characters in the path segment |
|
|
Matches zero or more path segments until the end of the path |
|
|
Matches a path segment and writes it to a variable called "name". |
|
|
Matches the expression |
|
|
Matches zero or more path segments to the end of the path and writes it to a variable named "path" . |
|
Captured URI variables can be accessed using an annotation @PathVariable
, as shown in the following example:
@GetMapping("/owners/{ownerId}/pets/{petId}")
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
// ...
}
@GetMapping("/owners/{ownerId}/pets/{petId}") fun findPet(@PathVariable ownerId: Long , @PathVariable petId: Long): Pet {
// ...
}
You can declare URI variables at the class and method level, as shown in the following example:
@Controller
@RequestMapping("/owners/{ownerId}")
public class OwnerController {
@GetMapping("/pets/{petId}") public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
// ...
}
}
- Display a URI at the class level.
- Display Method-level URI.
@Controller
@RequestMapping("/owners/{ownerId}")
class OwnerController {
@GetMapping("/pets/{petId}") fun findPet(@PathVariable ownerId: Long, @PathVariable petId: Long): Pet {
// ...
}
}
- Display URIs at the class level.
- Mapping URIs at the method level.
URI variables are converted to the appropriate type, otherwise an error occurs TypeMismatchException
. Simple types (int
, long
, Date
, and so on) are supported by default, but you can register support for any other data type.
You can explicitly name URI variables (for example, @PathVariable("customId")
), but you can also omit this information if the names are the same and your code compiles using debugging information or with the -parameters
compiler flag in Java 8.
The {*varName}
syntax declares a URI variable that matches zero or more remaining segments ways. For example, /resources/{*path}
matches all files in the /resources/
directory, and the "path"
variable reflects the full path in the /resources
.
Syntax {varName:regex}
declares a URI variable with a regular expression, which has the syntax: {varName:regex}
. For example, given the URL /spring-web-3.0.5.jar
, the following method retrieves the file name, version, and extension:
@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\ \.[a-z]+}") public void handle(@PathVariable String version, @PathVariable String ext) {
// ...
}
@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}") fun handle(@PathVariable version: String, @PathVariable ext: String) {
// ... }
URI path patterns can also contain inline placeholders ${...}
, which are recognized when run with PropertySourcesPlaceholderConfigurer
in local, system property sources, environment property sources, etc. You can use this, for example, to parameterize a base URL based on some external configuration.
PathPattern
and
PathPatternParser
to support URI path mapping. Both classes are found in
spring-web
and are specifically designed for use with HTTP URL paths in web applications where a large number of URI path patterns are matched at runtime.
Spring WebFlux does not support suffix mapping to an image - unlike Spring MVC, where a mapping such as /person
also matches /person.*
. To negotiate content based on a URL, if necessary, we recommend using a query parameter that is simpler, more explicit, and less vulnerable to URL path-based exploits.
Pattern comparison
If multiple patterns match a URL, they must be compared to find the best match. This is done using PathPattern.SPECIFICITY_COMPARATOR
, which looks for patterns that are more specific.
For each pattern, a score is calculated based on the number of URI identifier and wildcard variables, where URI -variable scores lower than wildcard. The sample with the lowest total score wins. If two samples have the same scores, the longer one is selected.
Catch-all samples (for example, **
, {*varName}
) are excluded from the count and is always sorted last. If two samples are both catch-all, the longer one is chosen.
Types of media consumed
You can narrow the query display based on the Content-Type
of the query , as shown in the following example:
@PostMapping(path = "/pets", consumes = "application /json") public void addPet(@RequestBody Pet pet) {
// ...
}
@PostMapping("/pets", consumes = ["application/json"]) fun addPet(@RequestBody pet: Pet) {
// ...
}
The "consumes" attribute also supports negation expressions - for example, !text/plain
means any content type other than text/plain
.
You can declare a generic consumes
attribute at the class level. However, unlike most other request mapping attributes, when used at the class level, the consumes
attribute at the method level overrides rather than extends the class-level declaration.
MediaType
provides constants for commonly used media types, such as
APPLICATION_JSON_VALUE
and
APPLICATION_XML_VALUE
.
Produced Media Types
You can narrow the range of requests that are displayed based on the Accept
request header and the list of content types that the controller method produces, as shown in the following example:
@GetMapping(path = "/pets/{petId}", produces = "application/json" )
@ResponseBody
public Pet getPet(@PathVariable String petId) {
// ...
}
@GetMapping("/pets/{petId}", produces = ["application/json"])
@ResponseBody
fun getPet(@PathVariable String petId): Pet {
// ...
}
The media type can determine the character set. Negable expressions are supported - for example, !text/plain
means any content type other than text/plain
.
You can declare a generic produces
attribute at the class level. However, unlike most other request mapping attributes, when used at the class level, the produces
attribute at the method level overrides rather than extends the class-level declaration.
MediaType
provides constants for commonly used media types, such as
APPLICATION_JSON_VALUE
and
APPLICATION_XML_VALUE
.
Parameters and headers
You can narrow the display of queries based on the conditions of the query parameters. You can check for the presence of a query parameter myParam
, its absence (!myParam
) or the presence of a specific value (myParam=myValue
). The following examples check for the presence of a parameter with a value:
@GetMapping(path = "/pets/{petId} ", params = "myParam=myValue")
public void findPet (@PathVariable String petId) {
// ...
}
- Make sure
myParam
is equal tomyValue
.
@GetMapping("/pets/{petId}", params = ["myParam=myValue"]) fun findPet(@PathVariable petId: String) {
// ...
}
- Make sure
myParam
is equal tomyValue
.
The same can be used with request header conditions , as shown in the following example:
@GetMapping(path = "/pets", headers = "myHeader=myValue") public void findPet( String petId){
// ...
}
- Make sure that
myHeader
is equal tomyValue
.
@GetMapping("/pets", headers = ["myHeader=myValue"]) fun findPet(@PathVariable petId: String) {
// ... }
- Make sure
myHeader
is equal tomyValue
.
HTTP methods HEAD, OPTIONS
@GetMapping
(and @RequestMapping(method=HttpMethod.GET)
) transparently support the HTTP HEAD method for mapping requests. The controller methods do not need to be changed. The response wrapper function used in HttpHandler
ensures that the Content-Length
header is set to the number of bytes written (without actually writing to the response).
Default The HTTP OPTIONS method is handled by setting the Allow
response header to the list of HTTP methods listed in all methods marked with the @RequestMapping
annotation.
In the case of the annotation @RequestMapping
without declaring HTTP methods, the Allow
header is set to GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS
. Controller methods should always declare supported HTTP methods (for example, using HTTP method-specific variants: @GetMapping
, @PostMapping
, and others).
You can explicitly map a method annotated with @RequestMapping
to the HEAD HTTP method and the OPTIONS HTTP method, but in ordinary cases this is not necessary.
Custom annotations
Spring WebFlux supports the use of compound annotations to display queries. These are annotations that are themselves @RequestMapping
meta-annotations and are composed to re-declar a subset (or all) of the attributes marked with the @RequestMapping
annotation for a narrower, more specific purpose.
@GetMapping
, @PostMapping
, @PutMapping
, @DeleteMapping
and @PatchMapping
are examples of compound annotations. These are provided because it is likely that most controller methods will need to be mapped to a specific HTTP method instead of using the @RequestMapping
annotation, which maps to all HTTP methods by default. If you need an example of compound annotations, take a look at how they are declared.
Spring WebFlux also supports custom query display attributes with custom query display logic. This is a more advanced option that requires subclassing RequestMappingHandlerMapping
and overriding the getCustomMethodCondition
method in which you can check the custom attribute and return your own RequestCondition
.
Explicit registration
You can programmatically register handler methods, which can be used for dynamic registration or in more complex cases, such as if there are different instances of the same handler under different URLs. The following example shows how to do this:
@Configuration
public class MyConfig {
@Autowired
public void setHandlerMapping(RequestMappingHandlerMapping mapping, UserHandler handler) (1)
throws NoSuchMethodException {
RequestMappingInfo info = RequestMappingInfo
.paths("/user/{id}").methods(RequestMethod.GET).build();
Method method = UserHandler.class.getMethod("getUser", Long.class);
mapping.registerMapping(info, handler, method);
}
}
- Inject the target handler and displaying the handler for controllers.
- Prepare the request display metadata.
- Get the handler method.
- Add registration.
@Configuration
class MyConfig {
@Autowired
fun setHandlerMapping(mapping: RequestMappingHandlerMapping, handler: UserHandler) {
val info = RequestMappingInfo.paths("/user/{id}").methods(RequestMethod.GET).build()
val method = UserHandler::class.java.getMethod("getUser", Long::class.java)
mapping.registerMapping(info, handler, method)
}
}
- Inject the target handler and handler mapping for controllers.
- Prepare the request mapping metadata.
- Get the handler method.
- Add registration .
Handler methods
Handler methods with the @RequestMapping
annotation have a flexible signature and can be chosen from a number of supported controller method arguments and return values.
Method Arguments
The following table lists the supported controller method arguments.
Reactive types (Reactor, RxJava, or others) are supported in arguments that require blocking I/O to resolve (for example, reading the request body). This is noted in the "Description" column. Reactive types are not expected for arguments that do not require blocking.
The java.util.Optional
argument from JDK 1.8 is supported as a method argument in combination with annotations that have a required
attribute (for example, @RequestParam
, @RequestHeader
and others), and is equivalent to required=false
.
Controller method argument | Description |
---|---|
|
Provides access to the full |
|
Provides access to an HTTP request or response. |
|
Provides access to the session. This does not start a new session unless attributes are added. Supports reactive types. |
|
The current authenticated user - possibly a concrete |
|
HTTP request method. |
|
The current locale of the request, determined by the most specific |
|
The time zone associated with the current request, as determined by the |
|
Designed to provide access to URI template variables. |
|
Designed to provide access to name-value pairs in path segments of URIs. |
|
Provides access to request parameters. Parameter values are converted to the declared type of the method's arguments. Note that using the |
|
Designed to provide access to request headers. Header values are converted to the declared type of the method argument. |
|
Designed to provide access to cookies. Cookie values are converted to the declared method argument type. |
|
Provides access to the HTTP request body. The body content is converted to the declared method argument type using |
|
Designed to provide access to request headers and body. The body is rendered using |
|
Provides access to the component in the |
|
Designed to provide access to the model that is used in HTML controllers and displayed in templates as part of the view rendering. |
|
Intended to provide access to an existing attribute in the model (of which an instance is created if it is missing) with data binding and validation. Note that using the |
|
Provide access to validation and data binding errors for the command object, i.e. argument |
|
Intended to mark completion of form processing, which causes session attributes declared through the |
|
Designed to prepare a URL associated with the host, port, scheme, and context path of the current request. |
|
Designed to provide access to any session attribute, as opposed to model attributes, stored in the session as a result of declaring the |
|
Designed to provide access to request attributes. |
Any other argument |
If a method argument does not match any of the above, it resolves by default to |
Return Values
The following table lists the supported return values of a controller method. Note that reactive types from libraries such as Reactor, RxJava or others are generally supported for all return values.
Return value of the controller method | Description |
---|---|
|
The return value is encoded through |
|
The return value specifies that the full response, including HTTP headers, and body will be encoded through |
|
Designed to return a response with headers and no body. |
|
The name of the view that should be recognized by |
|
A |
|
Attributes to add to the implicit model, with the view name implicitly determined based on the request path. |
|
An attribute added to the model when In this case, having views is implicitly determined based on the request path. Note that the |
|
API for scripts for visualizing models and views. |
|
A method with a return type of If neither of the above values are true, the |
|
Concerned with the generation of events sent by the server. The |
Any other return value |
If the return value does not match any of above, it is by default treated as the view name if it is a |
Type Conversion
Some annotated controller method arguments representing string-based request input (such as @RequestParam
, @RequestHeader
, @PathVariable
, @MatrixVariable
and @CookieValue
)) may require a type conversion if the argument is not declared as a String
.
In such cases, type conversion is applied automatically based on the configured converters. By default, simple types are supported (such as int
, long
, Date
and others). Type conversion can be configured using WebDataBinder
or by registering Formatters
with FormattingConversionService
.
A practical issue with type conversion is handling empty original string value. Such a value is considered missing if it becomes null
as a result of a type conversion. This may be true for Long
, UUID
and other target types. If you need to allow null
injection, then either use the required
flag in the argument annotation, or declare the argument as @Nullable
.
Matrix variables
In RFC 3986 pairs "name" are described -value" in path segments. In Spring WebFlux we call them "matrix variables", based on "old post" by Tim Berners-Lee, but they can also be called URI path parameters.
Matrix variables can appear in any path segment, with each variable separated by a semicolon and multiple values separated by commas - for example, "/cars;color=red,green;year=2012"
. Multiple values can also be specified through repeated variable names - for example, color=red;color=green;color=blue
) .
Unlike Spring MVC, in WebFlux the presence or absence of matrix variables in the URL does not affect the rendering of requests. In other words, you don't have to use a URI variable to mask the contents of the variable. However, if you need to access matrix variables from a controller method, you need to add a URI variable to the path segment where matrix variables are expected. The following example shows how to do this:
// GET /pets/42;q=11;r=22
@GetMapping("/pets/{petId}")
public void findPet(@PathVariable String petId, @MatrixVariable int q) {
// petId == 42
// q == 11
}
// GET /pets/42;q=11;r=22
@GetMapping("/pets/{petId}")
fun findPet(@PathVariable petId: String, @MatrixVariable q: Int) {
// petId == 42
// q == 11
}
Considering that all path segments can contain matrix variables, sometimes you may need to define which path variable the matrix variable should be in, as shown in the following example:
// GET /owners/42;q=11/pets/21;q=22
@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
@MatrixVariable(name="q", pathVar="ownerId") int q1,
@MatrixVariable(name="q", pathVar="petId") int q2) {
// q1 == 11
// q2 == 22
}
@GetMapping("/owners/{ownerId}/pets/{petId}")
fun findPet(
@MatrixVariable(name = "q", pathVar = "ownerId") q1: Int,
@MatrixVariable(name = "q", pathVar = "petId") q2: Int) {
// q1 == 11
// q2 == 22
}
You can define a matrix variable as optional and provide a default value, as shown in the following example:
// GET /pets/42
@GetMapping("/pets/{petId}")
public void findPet(@MatrixVariable(required=false, defaultValue="1") int q) {
// q == 1
}
// GET /pets/42
@GetMapping("/pets/{petId}")
fun findPet(@MatrixVariable(required = false, defaultValue = "1") q: Int) {
// q == 1
}
To get all the variables of a matrix, use MultiValueMap
as shown in the following example:
// GET /owners/42;q=11;r=12/pets/21;q=22;s=23
@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
@MatrixVariable MultiValueMap<String, String> matrixVars,
@MatrixVariable(pathVar="petId") MultiValueMap<String, String> petMatrixVars) {
// matrixVars: ["q" : [11,22], "r" : 12, "s" : 23]
// petMatrixVars: ["q" : 22, "s" : 23]
}
// GET /owners/42;q=11;r=12/pets/21;q=22;s=23
@GetMapping("/owners/{ownerId}/pets/{petId}")
fun findPet(
@MatrixVariable matrixVars: MultiValueMap<String, String>,
@MatrixVariable(pathVar="petId") petMatrixVars: MultiValueMap<String, String>) {
// matrixVars: ["q" : [11,22], "r" : 12, "s" : 23]
// petMatrixVars: ["q" : 22, "s" : 23]
}
@RequestParam
You can use the @RequestParam
annotation to bind request parameters to a method argument in the controller. The following code snippet demonstrates the usage:
@Controller
@RequestMapping("/pets")
public class EditPetForm {
// ...
@GetMapping
public String setupForm(@RequestParam("petId") int petId, Model model) {
Pet pet = this.clinic.loadPet(petId);
model.addAttribute("pet", pet);
return "petForm";
}
// ...
}
- Using the
@RequestParam
annotation.
import org.springframework.ui.set
@Controller
@RequestMapping("/pets")
class EditPetForm {
// ...
@GetMapping
fun setupForm(@RequestParam("petId") petId: Int, model: Model): String {
val pet = clinic.loadPet(petId)
model["pet"] = pet
return "petForm"
}
// ...
}
- Using the
@RequestParam
annotation.
ServerWebExchange
. Although the
@RequestParam
annotation binds only to request parameters, you can use data binding to apply request parameters, form data, and multipart elements to a command object.
Method parameters that use the @RequestParam
annotation are required by default, but you can also set a method parameter to be optional by setting the appropriate flag for @RequestParam
to false
or by declaring the argument with using the java.util.Optional
wrapper.
If the target method parameter type is not String
, type conversion is applied automatically.
If the @RequestParam
annotation is declared for the Map<String, String>
or MultiValueMap<String, String>
argument, the Map is populated with all request parameters.
Note that using the @RequestParam
annotation is optional - for example, to set its attributes. By default, any argument that is a simple value type (as defined by BeanUtils#isSimpleProperty) and is not resolved by any other argument resolver, is treated as if it were annotated with @RequestParam
.
@RequestHeader
You can use the @RequestHeader
annotation to bind a request header to a method argument in a controller.
The following example shows a request with headers:
Host localhost:8080 Accept text/html,application/xhtml+xml,application/xml;q=0.9 Accept-Language fr,en-gb;q=0.7,en;q=0.3 Accept-Encoding gzip,deflate Accept-Charset ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive 300
The following example code allows you to get the value of the Accept-headers Encoding
and Keep-Alive
:
@GetMapping("/demo")
public void handle(
@RequestHeader("Accept-Encoding") String encoding, (1)
@RequestHeader("Keep-Alive") long keepAlive) {
//...
}
- Get the
Accept-Encoding
header values. - Get the header values
Keep-Alive
.
@GetMapping("/demo")
fun handle(
@RequestHeader("Accept-Encoding") encoding: String, (1)
@RequestHeader("Keep-Alive") keepAlive: Long) {
//...
}
- Getting the values of the
Accept-Encoding
. - Get the values of the
Keep-Alive
header.
If the type of the target method parameter is not String
, type conversion is applied automatically.
If the @RequestHeader
annotation is used in the Map<String, String>
argument, MultiValueMap<String, String>
or HttpHeaders
, Map is filled with all header values.
@RequestHeader("Accept")
can be of type
String
, as well as
String[]
or
List<String>
.
@CookieValue
You can use the @CookieValue
annotation to bind an HTTP cookie value to method argument in the controller.
The following example shows a request with a cookie:
JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84
The following code example shows how to get the cookie value:
@GetMapping("/demo")
public void handle(@CookieValue("JSESSIONID") String cookie) {
//...
}
- Getting the cookie value.
@GetMapping("/demo")
fun handle(@CookieValue("JSESSIONID") cookie: String) {
//...
}
- Getting the cookie value.
If the target method parameter type is not String
, type conversion is applied automatically.
@ModelAttribute
You can use the @ModelAttribute
annotation on a method argument to access the attribute from the model or create an instance of it if it is missing. The model attribute is also superimposed with the values of query parameters and form fields, the names of which coincide with the names of the fields. This is called data binding, and it eliminates the need to parse and transform individual query parameters and form fields. The following example shows how a Pet
instance is bound:
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute Pet pet) { }
- Binding the
Pet
instance.
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
fun processSubmit(@ModelAttribute pet: Pet): String { }
- Binding the
Pet
instance.
The Pet
instance in the previous example is resolved as follows:
From the model, if it has already been added via
Model
.From an HTTP session via the
@SessionAttributes
annotation.From a call default constructor.
From a call to the "main constructor" with arguments corresponding to request parameters or form fields. Argument names are specified through the
@ConstructorProperties
annotation for JavaBeans or through run-time bytecode-stored parameter names.
After obtaining an instance of the model attribute, it is applied data binding. The WebExchangeDataBinder
class maps query parameter and form field names to field names of the target Object
. The corresponding fields are filled in after type conversion where necessary.
Data binding can lead to errors. By default, a WebExchangeBindException
is thrown, but to check for such errors in a controller method, you can add a BindingResult
argument directly next to the @ModelAttribute
annotation, as shown in the following example :
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) {
if (result.hasErrors()) {
return "petForm";
}
// ...
}
- Adding
BindingResult
.
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
fun processSubmit(@ModelAttribute("pet") pet: Pet, result: BindingResult): String {
if (result.hasErrors()) {
return "petForm"
}
// ...
}
- Adding
BindingResult
.
You can automatically apply validation after data binding by adding the javax.validation.Valid
annotation or the @Validated
annotation from Spring. The following example uses the @Valid
annotation:
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) {
if (result.hasErrors()) {
return "petForm";
}
// ...
}
- Using the
@Valid
annotation for the model attribute argument.
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
fun processSubmit(@Valid @ModelAttribute("pet") pet: Pet, result: BindingResult): String {
if (result.hasErrors()) {
return "petForm"
}
// ...
}
- Using the
@Valid
annotation for the model attribute argument.
Spring WebFlux, unlike Spring MVC, it supports reactive types in the model - for example, Mono<Account>
or io.reactivex.Single<Account>
. You can declare an argument marked with the @ModelAttribute
annotation with or without a reactive type wrapper function, after which it will be resolved accordingly to the actual value if necessary. However, note that in order to use the BindingResult
argument, you must declare the argument with the @ModelAttribute
annotation before it, without a reactive type wrapper function, as shown earlier. In addition, you can handle any errors through a reactive type, as shown in the following example:
@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 the @ModelAttribute
annotation is optional - for example, to set its attributes. By default, any argument that is not a simple value type (as defined by BeanUtils#isSimpleProperty) and is not resolved by any other argument resolver, is treated as if it were annotated with @ModelAttribute
.
@SessionAttributes
@SessionAttributes
is used to store model attributes in WebSession
between requests. This is a type-level annotation that declares the session attributes used by a particular controller. This typically lists the model attribute names or model attribute types that should be transparently stored in the session for subsequent access requests.
Consider the following example:
@Controller
@SessionAttributes("pet")
public class EditPetForm {
// ...
}
- Using the
@SessionAttributes
annotation.
@Controller
@SessionAttributes("pet")
class EditPetForm {
// ...
}
- Using the
@SessionAttributes
annotation.
On the first request, if a model attribute named pet
is added to the model, it is automatically promoted to and stored in the WebSession
. It remains there until another controller method uses the SessionStatus
method argument to clear the storage, as shown in the following example:
@Controller
@SessionAttributes("pet")
public class EditPetForm {
// ...
@PostMapping("/pets/{id}")
public String handle(Pet pet, BindingResult errors, SessionStatus status) {
if (errors.hasErrors()) {
// ...
}
status.setComplete();
// ...
}
}
}
- Using the
annotation @SessionAttributes
. - Using the
SessionStatus
variable.
@Controller
@SessionAttributes("pet")
class EditPetForm {
// ...
@PostMapping("/pets/{id}")
fun handle(pet: Pet, errors: BindingResult, status: SessionStatus): String {
if (errors.hasErrors()) {
// ...
}
status.setComplete()
// ...
}
}
- Using the
@SessionAttributes
annotation. - Using the
SessionStatus
variable.
@SessionAttribute
If you need access to pre-existing session attributes that are managed globally (that is, outside the controller - e.g. filter) and may or may not be present, you can use the @SessionAttribute
annotation on the method parameter, as shown in the following example:
@GetMapping("/")
public String handle(@SessionAttribute User user) {
// ...
}
- Use the
@SessionAttribute
annotation.
@GetMapping("/")
fun handle(@SessionAttribute user: User): String {
// ...
}
- Use the
@SessionAttribute
annotation.
For use cases that require adding or removing session attributes, note the ability to inject WebSession
into a controller method.
To temporarily store model attributes in a session as part of the controller workflow, consider using SessionAttributes
.
@RequestAttribute
Similar to the @SessionAttribute
annotation, you can use the @RequestAttribute
annotation to access existing attributes request created earlier (for example, WebFilter
), as shown in the following example:
@GetMapping("/")
public String handle(@RequestAttribute Client client) {
// ...
}
- Using the
@RequestAttribute
annotation.
@GetMapping("/")
fun handle(@RequestAttribute client: Client): String {
// ...
}
- Using annotation
@RequestAttribute
.
Multipart content
ServerWebExchange
provides access to multipart content. The best way to handle a file upload form (for example, from a browser) in a controller is to bind the data to a command object, as shown in the following example:
class MyForm {
private String name;
private MultipartFile file;
// ...
}
@Controller
public class FileUploadController {
@PostMapping("/form")
public String handleFormUpload(MyForm form, BindingResult errors) {
// ...
}
}
class MyForm(
val name: String,
val file: MultipartFile)
@Controller
class FileUploadController {
@PostMapping("/form")
fun handleFormUpload(form: MyForm, errors: BindingResult): String {
// ...
}
}
You can also send multi-part requests from non-browser clients in scripts using RESTful services. The following example uses a file along with JSON:
POST /someUrl Content-Type: multipart/mixed --edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp Content-Disposition: form-data; name="meta-data" Content-Type: application/json; charset=UTF-8 Content-Transfer-Encoding: 8bit { "name": "value" } --edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp Content-Disposition: form-data; name="file-data"; filename="file.properties" Content-Type: text/xml Content-Transfer-Encoding: 8bit ... File Data ...
You can access individual components using the @RequestPart
annotation, as shown in the following example:
@PostMapping("/")
public String handle(@RequestPart("meta-data") Part metadata,
@RequestPart("file-data") FilePart file) {
// ...
}
- Usage
@RequestPart
annotation to get metadata. - Use the
@RequestPart
annotation to get a file.
@PostMapping("/")
fun handle(@RequestPart("meta-data") Part metadata,
@RequestPart("file-data") FilePart file): String {
// ...
}
- Use the
@RequestPart
annotation to retrieve metadata. - Use the
@RequestPart
annotation to retrieve a file.
To deserialize the raw content of a component (for example, in JSON - similar to the @RequestBody
annotation), you can declare a specific target Object
instead of a Part
, as shown in the following example:
@PostMapping("/")
public String handle(@RequestPart("meta-data") MetaData metadata) {
// ...
}
- Use the
@RequestPart
annotation to retrieve metadata.
@PostMapping("/")
fun handle(@RequestPart("meta-data") metadata: MetaData): String {
// ...
}
- Using the
@RequestPart
annotation to obtain metadata.
You can use the @RequestPart
annotation in combination with the annotation javax.validation.Valid
or the @Validated
annotation from Spring, which will cause the standard Bean Validation to be applied. Validation errors result in a WebExchangeBindException
, resulting in a 400 response (BAD_REQUEST). The exception contains a BindingResult
with error details and can also be handled in a controller method by declaring an argument with an asynchronous wrapper function and then use the statements associated with the error:
@PostMapping("/")
public String handle(@Valid @RequestPart("meta-data") Mono<MetaData> metadata) {
// use one of the onError*...
}
@PostMapping("/")
fun handle(@Valid @RequestPart("meta-data") metadata: MetaData): String {
// ...
}
To access all multi-component data as a MultiValueMap
, you can use the @RequestBody
, as shown in the following example:
@PostMapping("/")
public String handle(@RequestBody Mono<MultiValueMap<String, Part>> parts) {
// ...
}
- Using the
@RequestBody
annotation.
@PostMapping("/")
fun handle(@RequestBody parts: MultiValueMap<String, Part>): String {
// ...
}
- Using the
@RequestBody
annotation.
For sequential streaming access to multi-part data, you can use the @RequestBody
annotation with Flux<Part>
(or Flow<Part>
in Kotlin), as shown in the following example:
@PostMapping("/")
public String handle(@RequestBody Flux<Part> parts) {
// ...
}
- Using the
@RequestBody
annotation.
@PostMapping("/")
fun handle(@RequestBody parts: Flow<Part>): String {
// ...
}
- Using the
@RequestBody
annotation.
@RequestBody
Annotation @RequestBody
can be used to read the request body and deserialize it into a object
Object HttpMessageReader. The following example uses an argument annotated with @RequestBody
:
@PostMapping("/accounts")
public void handle(@RequestBody Account account) {
// ...
}
@PostMapping("/accounts")
fun handle(@RequestBody account: Account) {
// ...
}
Unlike Spring MVC, in WebFlux the argument of a method marked with the @RequestBody
annotation supports reactive types and fully non-blocking reading and (client-server) streaming.
@PostMapping("/accounts")
public void handle(@RequestBody Mono<Account> account) {
// ...
}
@PostMapping("/accounts")
fun handle(@RequestBody accounts: Flow<Account>) {
// ...
}
You can use the HTTP message codecs option in the WebFlux configuration to configure or customize message readers.
The @RequestBody
annotation can be used in combination with the javax.validation.Valid
or the @Validated
annotation from Spring, which causes the standard Bean Validation to be applied. Validation errors cause WebExchangeBindException
, resulting in a 400 (BAD_REQUEST) response. The exception contains a BindingResult
with error details and can also be handled in a controller method by declaring an argument with an asynchronous wrapper function and then using the error-related statements:
@PostMapping("/accounts")
public void handle(@Valid @RequestBody Mono<Account> account) {
// используем один из операторов onError*...
}
@PostMapping("/accounts")
fun handle(@Valid @RequestBody account: Mono<Account>) {
// ...
}
HttpEntity
HttpEntity
is more or less identical to the @RequestBody
annotation in terms of usage, but is based on the entity -a container that opens the headers and body of the request. The following example uses HttpEntity
:
@PostMapping("/accounts")
public void handle(HttpEntity<Account> entity) {
// ...
}
@PostMapping("/accounts")
fun handle(entity: HttpEntity<Account>) {
// ...
}
@ResponseBody
The @ResponseBody
annotation can be used in our method so that the return is serialized into the response body via HttpMessageWriter. The following example shows how to do this:
@GetMapping("/accounts/{id}")
@ResponseBody
public Account handle() {
// ...
}
@GetMapping("/accounts/{id}")
@ResponseBody
fun handle(): Account {
// ...
}
@ResponseBody
is also supported at the class level, in which case it is inherited by all controller methods. This is the effect of the @RestController
annotation, which is nothing more than a meta annotation marked with the @Controller
and @ResponseBody annotations.
@ResponseBody
supports reactive types, which means you can return Reactor or RxJava types and receive the asynchronous values they create in response.
You can combine methods marked with the @ResponseBody
annotation with JSON serialization views.
You can use the HTTP message codecs option in the WebFlux configuration to configure or customize message readers.
ResponseEntity
The ResponseEntity
attribute is similar to the @ResponseBody
annotation, but with a status and headers. For example:
@GetMapping("/something")
public ResponseEntity<String> handle() {
String body = ... ;
String etag = ... ;
return ResponseEntity.ok().eTag(etag).body(body);
}
@GetMapping("/something")
fun handle(): ResponseEntity<String> {
val body: String = ...
val etag: String = ...
return ResponseEntity.ok().eTag(etag).build(body)
}
WebFlux supports the use of reactive type single-value for asynchronously creating ResponseEntity
and/or single- and multi-value reactive types for the body. This allows you to use different asynchronous responses with ResponseEntity
as follows:
ResponseEntity<Mono<T>>
orResponseEntity<Flux<T>>
immediately communicates the status and headers of the response, while the response body is provided asynchronously at a later point. We useMono
if the body consists of 0..1 values, orFlux
if it can create several values.Mono<ResponseEntity<T>>
provides all three parameters - response status, headers and body - asynchronously at a later point. This allows you to change the response status and headers depending on the results of asynchronous request processing.Mono<ResponseEntity<Mono<T>>>
orMono<ResponseEntity<Flux<T>>>
are another possible, although less common, alternative. They first provide the response status and headers asynchronously, and then secondly the response body, also asynchronously.
Jackson JSON
Spring provides library support Jackson JSON.
JSON Views
Spring WebFlux provides built-in support for serialization views from the Jackson library, which allows you to render exclusively a subset of all Object
fields. To use it with controller methods annotated with @ResponseBody
, or with the ResponseEntity
class, you can leverage the @JsonView
annotation from Jackson to activate the serialization view class, like shown in the following example:
@RestController
public class UserController {
@GetMapping("/user")
@JsonView(User.WithoutPasswordView.class)
public User getUser() {
return new User("eric", "7!jd#h23");
}
}
public class User {
public interface WithoutPasswordView {};
public interface WithPasswordView extends WithoutPasswordView {};
private String username;
private String password;
public User() {
}
public User(String username, String password) {
this.username = username;
this.password = password;
}
@JsonView(WithoutPasswordView.class)
public String getUsername() {
return this.username;
}
@JsonView(WithPasswordView.class)
public String getPassword() {
return this.password;
}
}
@RestController
class UserController {
@GetMapping("/user")
@JsonView(User.WithoutPasswordView::class)
fun getUser(): User {
return User("eric", "7!jd#h23")
}
}
class User(
@JsonView(WithoutPasswordView::class) val username: String,
@JsonView(WithPasswordView::class) val password: String
) {
interface WithoutPasswordView
interface WithPasswordView : WithoutPasswordView
}
@JsonView
allows you to use an array of view classes, but you can only specify one per controller method. Use a composite interface if you need to activate multiple views.
Model
You can use the @ModelAttribute
annotation:
As a method argument in methods with the
@RequestMapping
annotation to create or provide access to an object from the model and bind it to a request viaWebDataBinder
.As a method-level annotation in classes marked with the
@Controller
or@ControllerAdvice
annotations to help initialize the model before calling a method with the@RequestMapping
annotation.On a method with the
@RequestMapping
annotation to mark its return value as a model attribute .
This section describes methods annotated with @ModelAttribute
, or the second item from the previous list. A controller can have any number of methods marked with the @ModelAttribute
annotation. All such methods are called before methods marked with the @RequestMapping
annotation in the same controller. A method with the @ModelAttribute
annotation can also be shared between controllers through the @ControllerAdvice
annotation.
Methods with the @ModelAttribute
annotation have flexible method signatures. They support many of the same arguments as methods marked with the @RequestMapping
annotation, except for the @ModelAttribute
annotation itself or anything associated with the request body.
The following example uses a method marked with the @ModelAttribute
annotation:
@ModelAttribute
public void populateModel(@RequestParam String number, Model model) {
model.addAttribute(accountRepository.findAccount(number));
// add more ...
}
@ModelAttribute
fun populateModel(@RequestParam number: String, model: Model) {
model.addAttribute(accountRepository.findAccount(number))
// add more ...
}
The following example adds only one attribute:
@ModelAttribute
public Account addAccount(@RequestParam String number) {
return accountRepository.findAccount(number);
}
@ModelAttribute
fun addAccount(@RequestParam number: String): Account {
return accountRepository.findAccount(number);
}
agreements
. You can always assign an explicit name using the
addAttribute
overload or through the
name
attribute in the
@ModelAttribute
annotation (for the return value).
Spring WebFlux, unlike Spring MVC, explicitly supports reactive types in the model (for example, Mono<Account>
or io.reactivex.Single<Account>
). Such asynchronous model attributes can be transparently resolved (and the model updated) to their actual values during a call to @RequestMapping
if the argument with the @ModelAttribute
annotation is declared without a wrapper function, as shown in the following example:
@ModelAttribute
public void addAccount(@RequestParam String number) {
Mono<Account> accountMono = accountRepository.findAccount(number);
model.addAttribute("account", accountMono);
}
@PostMapping("/accounts")
public String handle(@ModelAttribute Account account, BindingResult errors) {
// ...
}
import org.springframework.ui.set
@ModelAttribute
fun addAccount(@RequestParam number: String) {
val accountMono: Mono<Account> = accountRepository.findAccount(number)
model["account"] = accountMono
}
@PostMapping("/accounts")
fun handle(@ModelAttribute account: Account, errors: BindingResult): String {
// ...
}
In addition, any model attributes that have a reactive wrapper function are resolved to their actual values (and the model is updated) immediately before the view is rendered.
It is also possible to use the @ModelAttribute
annotation as a method-level annotation for methods marked with the @RequestMapping
annotation, in which case the return value of the method marked annotation @RequestMapping
, interpreted as a model attribute. This is usually not required because this is the default behavior in HTML controllers unless the return value is a String
, which would otherwise be interpreted as the name of the view. The @ModelAttribute
annotation can also help you customize the model attribute name, as shown in the following example:
@GetMapping("/accounts/{id}")
@ModelAttribute("myAccount")
public Account handle() {
// ...
return account;
}
@GetMapping("/accounts/{id}")
@ModelAttribute("myAccount")
fun handle(): Account {
// ...
return account
}
DataBinder
Classes with annotation @Controller
or @ControllerAdvice
may have methods annotated with @InitBinder
to initialize WebDataBinder
instances. Those, in turn, are used to:
Binding request parameters (that is, form or request data) to a model object.
Converts
String
-based request values (such as request parameters, path variables, headers, cookies, etc.) to the target type of controller method arguments.Format model object values as
String
values when rendering HTML forms.
Methods with the @InitBinder
annotation can register Controller-specific java.beans.PropertyEditor
or Converter
and Formatter
components from Spring. You can also use the WebFlux Java configuration to register the Converter
and Formatter
types in a globally shared FormattingConversionService
.
Methods marked with the @InitBinder
annotation support many of the same arguments as methods with the @RequestMapping
annotation, with the exception of arguments annotated with @ModelAttribute
(command object). Typically they are declared with a WebDataBinder
argument to register and a void
return value. The following example uses the @InitBinder
annotation:
@Controller
public class FormController {
@InitBinder
public void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
dateFormat.setLenient(false);
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
}
// ...
}
- Using the
@InitBinder
annotation.
@Controller
class FormController {
@InitBinder
fun initBinder(binder: WebDataBinder) {
val dateFormat = SimpleDateFormat("yyyy-MM-dd")
dateFormat.isLenient = false
binder.registerCustomEditor(Date::class.java, CustomDateEditor(dateFormat, false))
}
// ...
}
Alternatively, when using Formatter
based configuration via the generic FormattingConversionService
, you can reuse the same approach and register Formatter
instances code> specific to the controller, as shown in the following example:
@Controller
public class FormController {
@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
}
// ...
}
- Add a custom formatter (in this case
DateFormatter
).
@Controller
class FormController {
@InitBinder
fun initBinder(binder: WebDataBinder) {
binder.addCustomFormatter(DateFormatter("yyyy-MM-dd"))
}
// ...
}
- Adding a custom formatter (in this case
DateFormatter
).
Model structure
In the context of web applications, data binding involves binding HTTP request parameters (that is, form data or request parameters) to properties of a model object and its sub-objects.
Only public
properties corresponding to are open for data binding JavaBeans naming conventions - for example, the public String getFirstName()
and public void setFirstName(String)
methods for the firstName
property.
By default, Spring allows binding to all public properties in the model object graph. This means that you need to think carefully about what public properties the model has, since the client can target any public property, even those not intended for the use case.
For example, if you have an HTTP data endpoint -forms, a malicious client can pass values for properties that exist in the model's object graph but are not part of the HTML form presented in the browser. This may result in the model object and any of its sub-objects being set to data that is not expected to be updated.
The recommended approach is to use a specialized model object that opens only those properties that are relevant to form submission. For example, in a form to change a user's email address, the model object must declare a minimal set of properties, as in the following ChangeEmailForm
.
public class ChangeEmailForm {
private String oldEmailAddress;
private String newEmailAddress;
public void setOldEmailAddress(String oldEmailAddress) {
this.oldEmailAddress = oldEmailAddress;
}
public String getOldEmailAddress() {
return this.oldEmailAddress;
}
public void setNewEmailAddress(String newEmailAddress) {
this.newEmailAddress = newEmailAddress;
}
public String getNewEmailAddress() {
return this.newEmailAddress;
}
}
If you are unable or unwilling to use a specialized model object for each use case of data binding, then mustrestrict the properties that are allowed for data binding. Ideally, this can be achieved by registering allowed field patterns using the setAllowedFields()
method in WebDataBinder
.
For example, to register valid field patterns in your application, you can implement a method marked with the @InitBinder
annotation in a component with the @Controller
or @ControllerAdvice
annotation, as shown below:
@Controller
public class ChangeEmailController {
@InitBinder
void initBinder(WebDataBinder binder) {
binder.setAllowedFields("oldEmailAddress", "newEmailAddress");
}
// @RequestMapping methods, etc.
}
In addition to registering valid field templates, you can also register disallowed field patterns using the setDisallowedFields()
method in DataBinder
and its subclasses. However, note that the "allow list" is safer than the "deny list". Therefore, using setAllowedFields()
should be preferred over using setDisallowedFields()
.
Note that matching against allowed field patterns is case sensitive; while pattern matching of invalid fields is not. Additionally, a field that matches a pattern of invalid fields will not be accepted, even if it also matches a pattern in the list of valid fields.
It is very important to get it right configure valid and invalid field templates when directly opening the domain model for data binding. Otherwise, it will be a big security risk.
Additionally, it is strongly recommended not to use types from your domain model, such as JPA or Hibernate entities, as a model object in data binding scenarios.
Managing exceptions
Classes with the @Controller
and @ControllerAdvice annotation can have methods marked with the @ExceptionHandler
annotation, to handle exceptions from controller methods. The following example contains this handler method:
@Controller
public class SimpleController {
// ...
@ExceptionHandler
public ResponseEntity<String> handle(IOException ex) {
// ...
}
}
- Declaration of the
@ExceptionHandler
annotation.
@Controller
class SimpleController {
// ...
@ExceptionHandler
fun handle(ex: IOException): ResponseEntity<String> {
// ...
}
}
- Annotation declaration
@ExceptionHandler
.
An exception can be a propagated high-level exception (for example, a directly thrown IOException
) or a nested cause inside a wrapper exception (for example, IOException
wrapped inside IllegalStateException
).
For matching exception types, it is preferable to declare the target exception as a method argument, like shown in the previous example. Additionally, the annotation declaration can narrow down the types of exceptions that must match. We generally recommend being as specific as possible in the argument signature and declaring the main root exception mappings to the @ControllerAdvice
annotation in the appropriate order.
@ExceptionHandler
annotation in WebFlux supports the same method arguments and return values as a method with the
@RequestMapping
annotation, except for the method arguments associated with the request body and with the
@ModelAttribute
annotation.
Support for methods annotated with @ExceptionHandler
in Spring WebFlux is provided by the HandlerAdapter
for methods with the @RequestMapping
.
Handling exceptions in the REST API
A common requirement for REST-based services is to include error information in the response body. The Spring Framework does not do this automatically because the inclusion of error information in the response body is application-specific. However, the @RestController
annotation can use methods marked with the @ExceptionHandler
annotation with a ResponseEntity
return value to set the response status and body. Such methods can also be declared in classes with the @ControllerAdvice
annotation to apply them globally.
ResponseEntityExceptionHandler
from Spring MVC, since WebFlux only throws
ResponseStatusException
(or their subclasses) and they do not need to be converted to HTTP status code.
ControllerAdvice
Typically, methods marked with the @ExceptionHandler
, @InitBinder
and @ModelAttribute
annotations are used in within the @Controller
class (or class hierarchy) in which they are declared. If you want such methods to apply more globally (across all controllers), you can declare them in a class annotated with @ControllerAdvice
or @RestControllerAdvice
.
The @ControllerAdvice
annotation is annotated with @Component
, which means that such classes can be registered as Spring beans via component scanning. The @RestControllerAdvice
annotation is a compound annotation that is marked with both the @ControllerAdvice
annotation and the @ResponseBody
annotation, which means that methods with the annotation @ExceptionHandler
are rendered in the response body through message mapping (as opposed to view resolution or template rendering).
At startup, framework classes for methods annotated with @RequestMapping
and @ExceptionHandler
detect Spring beans annotated with @ControllerAdvice
and then apply their methods at runtime. Global methods annotated with @ExceptionHandler
(from the @ControllerAdvice
annotation) are applied after local ones (from the @Controller
annotation ). In contrast, global methods with the @ModelAttribute
and @InitBinder
annotations are applied before local ones.
By default, methods marked with the @ControllerAdvice
apply to every request (that is, all controllers), but you can narrow down the controllers by using annotation attributes, as shown in the following example:
// Target all controllers annotated with @RestController @RestController
@ControllerAdvice(annotations = RestController.class)
public class ExampleAdvice1 {}
// Target all controllers in certain packages
@ControllerAdvice("org.example.controllers")
public class ExampleAdvice2 {}
// Target all controllers assigned to specific classes
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
public class ExampleAdvice3 {}
// Target all controllers annotated with @RestController
@ControllerAdvice(annotations = [RestController::class])
public class ExampleAdvice1 {}
// Select all controllers in the target certain packages
@ControllerAdvice("org.example.controllers")
public class ExampleAdvice2 {}
// Target all controllers assigned to certain classes
@ControllerAdvice(assignableTypes = [ControllerInterface::class, AbstractController::class])
public class ExampleAdvice3 {}
The selectors in the previous example are evaluated during execution and can negatively impact performance when used extensively. For more information, see the javadoc annotation @ControllerAdvice
.
GO TO FULL VERSION