0
# Annotation-Based Controllers
1
2
Spring WebFlux supports traditional Spring MVC-style controllers enhanced with reactive return types. This programming model allows developers familiar with Spring MVC to adopt reactive programming while maintaining familiar annotation-based patterns.
3
4
## Controller Annotations
5
6
### Class-Level Annotations
7
8
```java { .api }
9
@RestController
10
public @interface RestController {
11
@AliasFor(annotation = Controller.class)
12
String value() default "";
13
}
14
15
@Controller
16
@Component
17
public @interface Controller {
18
String value() default "";
19
}
20
```
21
22
### Request Mapping Annotations
23
24
```java { .api }
25
@RequestMapping
26
public @interface RequestMapping {
27
String name() default "";
28
String[] value() default {};
29
String[] path() default {};
30
RequestMethod[] method() default {};
31
String[] params() default {};
32
String[] headers() default {};
33
String[] consumes() default {};
34
String[] produces() default {};
35
}
36
37
@GetMapping
38
public @interface GetMapping {
39
String name() default "";
40
String[] value() default {};
41
String[] path() default {};
42
String[] params() default {};
43
String[] headers() default {};
44
String[] consumes() default {};
45
String[] produces() default {};
46
}
47
48
@PostMapping
49
public @interface PostMapping { /* same structure as GetMapping */ }
50
51
@PutMapping
52
public @interface PutMapping { /* same structure as GetMapping */ }
53
54
@DeleteMapping
55
public @interface DeleteMapping { /* same structure as GetMapping */ }
56
57
@PatchMapping
58
public @interface PatchMapping { /* same structure as GetMapping */ }
59
```
60
61
## Parameter Binding Annotations
62
63
### Path Variables
64
65
```java { .api }
66
@PathVariable
67
public @interface PathVariable {
68
@AliasFor("name")
69
String value() default "";
70
String name() default "";
71
boolean required() default true;
72
}
73
```
74
75
### Request Parameters
76
77
```java { .api }
78
@RequestParam
79
public @interface RequestParam {
80
@AliasFor("name")
81
String value() default "";
82
String name() default "";
83
boolean required() default true;
84
String defaultValue() default ValueConstants.DEFAULT_NONE;
85
}
86
```
87
88
### Request Body
89
90
```java { .api }
91
@RequestBody
92
public @interface RequestBody {
93
boolean required() default true;
94
}
95
```
96
97
### Request Headers
98
99
```java { .api }
100
@RequestHeader
101
public @interface RequestHeader {
102
@AliasFor("name")
103
String value() default "";
104
String name() default "";
105
boolean required() default true;
106
String defaultValue() default ValueConstants.DEFAULT_NONE;
107
}
108
```
109
110
### Cookie Values
111
112
```java { .api }
113
@CookieValue
114
public @interface CookieValue {
115
@AliasFor("name")
116
String value() default "";
117
String name() default "";
118
boolean required() default true;
119
String defaultValue() default ValueConstants.DEFAULT_NONE;
120
}
121
```
122
123
## Response Handling
124
125
### Response Annotations
126
127
```java { .api }
128
@ResponseBody
129
public @interface ResponseBody {
130
}
131
132
@ResponseStatus
133
public @interface ResponseStatus {
134
HttpStatus code() default HttpStatus.INTERNAL_SERVER_ERROR;
135
@AliasFor("code")
136
HttpStatus value() default HttpStatus.INTERNAL_SERVER_ERROR;
137
String reason() default "";
138
}
139
```
140
141
## Reactive Return Types
142
143
### Single Values
144
145
```java { .api }
146
// Reactor Mono for single values
147
public abstract class Mono<T> implements CorePublisher<T> {
148
public static <T> Mono<T> just(T data);
149
public static <T> Mono<T> empty();
150
public static <T> Mono<T> error(Throwable error);
151
public static <T> Mono<T> fromCallable(Callable<? extends T> supplier);
152
public static <T> Mono<T> fromSupplier(Supplier<? extends T> supplier);
153
154
public <R> Mono<R> map(Function<? super T, ? extends R> mapper);
155
public <R> Mono<R> flatMap(Function<? super T, ? extends Mono<? extends R>> transformer);
156
public Mono<T> filter(Predicate<? super T> tester);
157
public Mono<T> switchIfEmpty(Mono<? extends T> alternate);
158
}
159
```
160
161
### Multiple Values
162
163
```java { .api }
164
// Reactor Flux for multiple values
165
public abstract class Flux<T> implements CorePublisher<T> {
166
public static <T> Flux<T> just(T... data);
167
public static <T> Flux<T> empty();
168
public static <T> Flux<T> error(Throwable error);
169
public static <T> Flux<T> fromIterable(Iterable<? extends T> it);
170
public static <T> Flux<T> fromArray(T[] array);
171
172
public <R> Flux<R> map(Function<? super T, ? extends R> mapper);
173
public <R> Flux<R> flatMap(Function<? super T, ? extends Publisher<? extends R>> mapper);
174
public Flux<T> filter(Predicate<? super T> predicate);
175
public Flux<T> take(long n);
176
public Flux<T> skip(long n);
177
public Mono<List<T>> collectList();
178
}
179
```
180
181
### Response Entity
182
183
```java { .api }
184
public class ResponseEntity<T> extends HttpEntity<T> {
185
public ResponseEntity(HttpStatus status);
186
public ResponseEntity(T body, HttpStatus status);
187
public ResponseEntity(MultiValueMap<String, String> headers, HttpStatus status);
188
public ResponseEntity(T body, MultiValueMap<String, String> headers, HttpStatus status);
189
190
public HttpStatus getStatusCode();
191
192
public static BodyBuilder status(HttpStatus status);
193
public static <T> ResponseEntity<T> ok(T body);
194
public static <T> ResponseEntity<T> created(URI location);
195
public static <T> ResponseEntity<T> notFound();
196
public static <T> ResponseEntity<T> badRequest();
197
}
198
```
199
200
## Usage Examples
201
202
### Basic CRUD Controller
203
204
```java
205
@RestController
206
@RequestMapping("/api/users")
207
public class UserController {
208
209
private final UserService userService;
210
211
public UserController(UserService userService) {
212
this.userService = userService;
213
}
214
215
@GetMapping("/{id}")
216
public Mono<ResponseEntity<User>> getUser(@PathVariable String id) {
217
return userService.findById(id)
218
.map(ResponseEntity::ok)
219
.defaultIfEmpty(ResponseEntity.notFound().build());
220
}
221
222
@GetMapping
223
public Flux<User> getAllUsers(
224
@RequestParam(defaultValue = "0") int page,
225
@RequestParam(defaultValue = "10") int size) {
226
return userService.findAll(page, size);
227
}
228
229
@PostMapping
230
public Mono<ResponseEntity<User>> createUser(@RequestBody Mono<User> userMono) {
231
return userMono
232
.flatMap(userService::save)
233
.map(user -> ResponseEntity.created(URI.create("/api/users/" + user.getId())).body(user));
234
}
235
236
@PutMapping("/{id}")
237
public Mono<ResponseEntity<User>> updateUser(
238
@PathVariable String id,
239
@RequestBody Mono<User> userMono) {
240
return userMono
241
.flatMap(user -> userService.update(id, user))
242
.map(ResponseEntity::ok)
243
.defaultIfEmpty(ResponseEntity.notFound().build());
244
}
245
246
@DeleteMapping("/{id}")
247
public Mono<ResponseEntity<Void>> deleteUser(@PathVariable String id) {
248
return userService.deleteById(id)
249
.then(Mono.just(ResponseEntity.noContent().<Void>build()));
250
}
251
}
252
```
253
254
### Stream Processing Controller
255
256
```java
257
@RestController
258
@RequestMapping("/api/data")
259
public class DataStreamController {
260
261
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
262
public Flux<String> streamData() {
263
return Flux.interval(Duration.ofSeconds(1))
264
.map(i -> "Data point: " + i)
265
.take(10);
266
}
267
268
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
269
public Mono<String> uploadFile(@RequestPart("file") Mono<FilePart> fileMono) {
270
return fileMono
271
.flatMap(file -> file.transferTo(Paths.get("/tmp/" + file.filename())))
272
.then(Mono.just("File uploaded successfully"));
273
}
274
275
@GetMapping("/search")
276
public Flux<SearchResult> search(
277
@RequestParam String query,
278
@RequestHeader(name = "Accept-Language", defaultValue = "en") String language) {
279
return searchService.search(query, language);
280
}
281
}
282
```
283
284
### Exception Handling
285
286
```java
287
@RestController
288
public class UserController {
289
290
@GetMapping("/users/{id}")
291
public Mono<User> getUser(@PathVariable String id) {
292
return userService.findById(id)
293
.switchIfEmpty(Mono.error(new UserNotFoundException("User not found: " + id)));
294
}
295
296
@ExceptionHandler(UserNotFoundException.class)
297
@ResponseStatus(HttpStatus.NOT_FOUND)
298
public Mono<ErrorResponse> handleUserNotFound(UserNotFoundException ex) {
299
return Mono.just(new ErrorResponse("USER_NOT_FOUND", ex.getMessage()));
300
}
301
302
@ExceptionHandler(ValidationException.class)
303
@ResponseStatus(HttpStatus.BAD_REQUEST)
304
public Mono<ErrorResponse> handleValidation(ValidationException ex) {
305
return Mono.just(new ErrorResponse("VALIDATION_ERROR", ex.getMessage()));
306
}
307
}
308
```
309
310
### Custom Content Types
311
312
```java
313
@RestController
314
public class MediaController {
315
316
@GetMapping(value = "/xml-data", produces = MediaType.APPLICATION_XML_VALUE)
317
public Mono<XmlData> getXmlData() {
318
return dataService.getXmlData();
319
}
320
321
@PostMapping(value = "/json-data",
322
consumes = MediaType.APPLICATION_JSON_VALUE,
323
produces = MediaType.APPLICATION_JSON_VALUE)
324
public Mono<JsonResponse> processJsonData(@RequestBody Mono<JsonRequest> request) {
325
return request.flatMap(dataService::processJson);
326
}
327
328
@GetMapping(value = "/csv-export", produces = "text/csv")
329
public Flux<String> exportCsv() {
330
return dataService.getAllRecords()
331
.map(record -> String.join(",", record.getFields()));
332
}
333
}
334
```
335
336
## Integration with Validation
337
338
```java
339
@RestController
340
@Validated
341
public class ValidatedController {
342
343
@PostMapping("/users")
344
public Mono<User> createUser(@Valid @RequestBody Mono<User> userMono) {
345
return userMono.flatMap(userService::save);
346
}
347
348
@GetMapping("/users/{id}")
349
public Mono<User> getUser(@PathVariable @Pattern(regexp = "^[0-9]+$") String id) {
350
return userService.findById(id);
351
}
352
353
@GetMapping("/users")
354
public Flux<User> getUsers(
355
@RequestParam @Min(0) int page,
356
@RequestParam @Min(1) @Max(100) int size) {
357
return userService.findAll(page, size);
358
}
359
}
360
```
361
362
## Request Processing Features
363
364
### Content Negotiation
365
366
Spring WebFlux automatically handles content negotiation based on Accept headers and produces/consumes attributes in mapping annotations.
367
368
### Asynchronous Request Processing
369
370
All controller methods returning reactive types are processed asynchronously, allowing the server to handle thousands of concurrent requests with minimal thread usage.
371
372
### Backpressure Support
373
374
When using Flux return types, Spring WebFlux automatically applies backpressure, ensuring that slow consumers don't overwhelm the system.