0
# Controller Annotations
1
2
Spring MVC's annotation-driven programming model provides a declarative approach to request handling using @Controller, @RequestMapping, and related annotations. This system supports method parameters, return values, exception handling, and asynchronous processing.
3
4
## Capabilities
5
6
### RequestMappingHandlerMapping
7
8
Creates RequestMappingInfo instances from @RequestMapping annotations and manages the mapping between requests and handler methods.
9
10
```java { .api }
11
/**
12
* Creates RequestMappingInfo instances from type and method-level @RequestMapping annotations.
13
*/
14
public class RequestMappingHandlerMapping extends AbstractHandlerMethodMapping<RequestMappingInfo> {
15
16
/** Enable/disable suffix pattern matching (deprecated) */
17
public void setUseSuffixPatternMatch(boolean useSuffixPatternMatch);
18
19
/** Enable/disable trailing slash matching */
20
public void setUseTrailingSlashMatch(boolean useTrailingSlashMatch);
21
22
/** Set content negotiation manager */
23
public void setContentNegotiationManager(ContentNegotiationManager contentNegotiationManager);
24
25
/** Set path matcher for URL pattern matching */
26
public void setPathMatcher(PathMatcher pathMatcher);
27
28
/** Set URL path helper */
29
public void setUrlPathHelper(UrlPathHelper urlPathHelper);
30
}
31
```
32
33
### RequestMappingHandlerAdapter
34
35
HandlerAdapter that supports handlers with @RequestMapping annotations, managing method invocation, parameter resolution, and return value handling.
36
37
```java { .api }
38
/**
39
* Extension of AbstractHandlerMethodAdapter that supports @RequestMapping annotated HandlerMethods.
40
*/
41
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter {
42
43
/** Set custom argument resolvers */
44
public void setCustomArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers);
45
46
/** Set custom return value handlers */
47
public void setCustomReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers);
48
49
/** Configure message converters for request/response body conversion */
50
public void setMessageConverters(List<HttpMessageConverter<?>> messageConverters);
51
52
/** Set content negotiation manager */
53
public void setContentNegotiationManager(ContentNegotiationManager contentNegotiationManager);
54
55
/** Set request body advice for @RequestBody processing */
56
public void setRequestBodyAdvice(List<RequestBodyAdvice> requestBodyAdvice);
57
58
/** Set response body advice for @ResponseBody processing */
59
public void setResponseBodyAdvice(List<ResponseBodyAdvice<?>> responseBodyAdvice);
60
61
/** Set synchronous task executor */
62
public void setTaskExecutor(AsyncTaskExecutor taskExecutor);
63
64
/** Set session attribute store */
65
public void setSessionAttributeStore(SessionAttributeStore sessionAttributeStore);
66
67
/** Enable/disable parameter name discovery from debug info */
68
public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer);
69
}
70
```
71
72
**Usage Example:**
73
74
```java
75
@Configuration
76
@EnableWebMvc
77
public class WebConfig implements WebMvcConfigurer {
78
79
@Override
80
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
81
resolvers.add(new CustomArgumentResolver());
82
}
83
84
@Override
85
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {
86
handlers.add(new CustomReturnValueHandler());
87
}
88
89
@Override
90
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
91
converters.add(new MappingJackson2HttpMessageConverter());
92
converters.add(new StringHttpMessageConverter());
93
}
94
}
95
```
96
97
### Controller Method Examples
98
99
```java
100
@Controller
101
@RequestMapping("/api/users")
102
public class UserController {
103
104
@Autowired
105
private UserService userService;
106
107
@GetMapping
108
@ResponseBody
109
public List<User> getUsers(
110
@RequestParam(defaultValue = "0") int page,
111
@RequestParam(defaultValue = "10") int size,
112
@RequestParam(required = false) String search) {
113
return userService.findUsers(page, size, search);
114
}
115
116
@GetMapping("/{id}")
117
@ResponseBody
118
public ResponseEntity<User> getUser(@PathVariable Long id) {
119
return userService.findById(id)
120
.map(ResponseEntity::ok)
121
.orElse(ResponseEntity.notFound().build());
122
}
123
124
@PostMapping
125
@ResponseBody
126
public ResponseEntity<User> createUser(
127
@Valid @RequestBody CreateUserRequest request,
128
BindingResult bindingResult,
129
HttpServletRequest httpRequest) {
130
131
if (bindingResult.hasErrors()) {
132
return ResponseEntity.badRequest().build();
133
}
134
135
User user = userService.create(request);
136
URI location = ServletUriComponentsBuilder
137
.fromCurrentRequest()
138
.path("/{id}")
139
.buildAndExpand(user.getId())
140
.toUri();
141
142
return ResponseEntity.created(location).body(user);
143
}
144
145
@PutMapping("/{id}")
146
@ResponseBody
147
public ResponseEntity<User> updateUser(
148
@PathVariable Long id,
149
@Valid @RequestBody UpdateUserRequest request) {
150
151
return userService.update(id, request)
152
.map(ResponseEntity::ok)
153
.orElse(ResponseEntity.notFound().build());
154
}
155
156
@DeleteMapping("/{id}")
157
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
158
if (userService.delete(id)) {
159
return ResponseEntity.noContent().build();
160
}
161
return ResponseEntity.notFound().build();
162
}
163
164
@ExceptionHandler(ValidationException.class)
165
@ResponseStatus(HttpStatus.BAD_REQUEST)
166
@ResponseBody
167
public ErrorResponse handleValidation(ValidationException ex) {
168
return new ErrorResponse("VALIDATION_ERROR", ex.getMessage());
169
}
170
}
171
```
172
173
### Asynchronous Processing
174
175
#### ResponseBodyEmitter
176
177
Controller method return value type for asynchronous request processing where the response is written incrementally.
178
179
```java { .api }
180
/**
181
* Controller method return value type for asynchronous request processing.
182
*/
183
public class ResponseBodyEmitter {
184
185
/** Create emitter with default timeout */
186
public ResponseBodyEmitter();
187
188
/** Create emitter with specified timeout in milliseconds */
189
public ResponseBodyEmitter(Long timeout);
190
191
/** Send an object to the response */
192
public void send(Object object) throws IOException;
193
194
/** Send an object with specific media type */
195
public void send(Object object, MediaType mediaType) throws IOException;
196
197
/** Complete the response */
198
public void complete();
199
200
/** Complete with error */
201
public void completeWithError(Throwable ex);
202
203
/** Register timeout callback */
204
public void onTimeout(Runnable callback);
205
206
/** Register error callback */
207
public void onError(Consumer<Throwable> callback);
208
209
/** Register completion callback */
210
public void onCompletion(Runnable callback);
211
212
/** Set timeout value */
213
public void setTimeout(Long timeout);
214
}
215
```
216
217
#### SseEmitter
218
219
Specialization of ResponseBodyEmitter for Server-Sent Events.
220
221
```java { .api }
222
/**
223
* Specialization of ResponseBodyEmitter for Server-Sent Events.
224
*/
225
public class SseEmitter extends ResponseBodyEmitter {
226
227
/** Create SSE emitter with default timeout */
228
public SseEmitter();
229
230
/** Create SSE emitter with specified timeout */
231
public SseEmitter(Long timeout);
232
233
/** Send SSE event */
234
public void send(SseEventBuilder builder) throws IOException;
235
236
/** Create SSE event builder */
237
public SseEventBuilder event();
238
239
/** Static method to create event builder */
240
public static SseEventBuilder event();
241
242
/**
243
* Builder for SSE events.
244
*/
245
public static class SseEventBuilder {
246
/** Set event ID */
247
public SseEventBuilder id(String id);
248
249
/** Set event name */
250
public SseEventBuilder name(String name);
251
252
/** Set event data */
253
public SseEventBuilder data(Object object);
254
255
/** Set event data with media type */
256
public SseEventBuilder data(Object object, MediaType mediaType);
257
258
/** Set retry timeout */
259
public SseEventBuilder retry(long reconnectTime);
260
261
/** Set comment */
262
public SseEventBuilder comment(String comment);
263
}
264
}
265
```
266
267
**Async Processing Examples:**
268
269
```java
270
@Controller
271
public class StreamingController {
272
273
@GetMapping("/stream")
274
public ResponseBodyEmitter streamData() {
275
ResponseBodyEmitter emitter = new ResponseBodyEmitter(30000L);
276
277
CompletableFuture.runAsync(() -> {
278
try {
279
for (int i = 0; i < 10; i++) {
280
emitter.send("data " + i);
281
Thread.sleep(1000);
282
}
283
emitter.complete();
284
} catch (Exception ex) {
285
emitter.completeWithError(ex);
286
}
287
});
288
289
return emitter;
290
}
291
292
@GetMapping("/events")
293
public SseEmitter streamEvents() {
294
SseEmitter emitter = new SseEmitter(Long.MAX_VALUE);
295
296
eventService.subscribe(event -> {
297
try {
298
emitter.send(SseEmitter.event()
299
.id(event.getId())
300
.name(event.getType())
301
.data(event.getData()));
302
} catch (IOException ex) {
303
emitter.completeWithError(ex);
304
}
305
});
306
307
emitter.onCompletion(() -> eventService.unsubscribe());
308
emitter.onTimeout(() -> eventService.unsubscribe());
309
310
return emitter;
311
}
312
313
@GetMapping("/callable")
314
public Callable<String> processAsync() {
315
return () -> {
316
Thread.sleep(2000); // Simulate long processing
317
return "Async result";
318
};
319
}
320
321
@GetMapping("/deferred")
322
public DeferredResult<String> processDeferredResult() {
323
DeferredResult<String> deferredResult = new DeferredResult<>(5000L);
324
325
CompletableFuture.supplyAsync(() -> {
326
// Simulate async processing
327
try {
328
Thread.sleep(2000);
329
return "Deferred result";
330
} catch (InterruptedException e) {
331
throw new RuntimeException(e);
332
}
333
}).whenComplete((result, throwable) -> {
334
if (throwable != null) {
335
deferredResult.setErrorResult(throwable);
336
} else {
337
deferredResult.setResult(result);
338
}
339
});
340
341
return deferredResult;
342
}
343
}
344
```
345
346
### MvcUriComponentsBuilder
347
348
Utility class for building URIs to controller methods using method references.
349
350
```java { .api }
351
/**
352
* UriComponentsBuilder with additional static factory methods to create URLs based on controller classes and methods.
353
*/
354
public class MvcUriComponentsBuilder extends UriComponentsBuilder {
355
356
/** Create URI builder from controller class */
357
public static UriComponentsBuilder fromController(Class<?> controllerType);
358
359
/** Create URI builder from controller method name */
360
public static UriComponentsBuilder fromMethodName(Class<?> controllerType, String methodName, Object... args);
361
362
/** Create URI builder from method call info */
363
public static UriComponentsBuilder fromMethodCall(Object info);
364
365
/** Create URI builder from mapping name */
366
public static UriComponentsBuilder fromMappingName(String mappingName);
367
368
/** Create URI builder from method call */
369
public static <T> UriComponentsBuilder fromMethodCall(Class<T> controllerType, Function<T, ?> methodCall);
370
}
371
```
372
373
**Usage Examples:**
374
375
```java
376
@Controller
377
@RequestMapping("/users")
378
public class UserController {
379
380
@GetMapping("/{id}")
381
public String getUser(@PathVariable Long id, Model model) {
382
User user = userService.findById(id);
383
model.addAttribute("user", user);
384
385
// Build URI to edit user
386
String editUri = MvcUriComponentsBuilder
387
.fromMethodName(UserController.class, "editUser", id)
388
.toUriString();
389
model.addAttribute("editUri", editUri);
390
391
return "user-detail";
392
}
393
394
@GetMapping("/{id}/edit")
395
public String editUser(@PathVariable Long id, Model model) {
396
// Implementation
397
return "user-edit";
398
}
399
400
@PostMapping
401
public String createUser(@Valid User user, BindingResult result) {
402
if (result.hasErrors()) {
403
return "user-form";
404
}
405
User savedUser = userService.save(user);
406
407
// Redirect to user detail page
408
return "redirect:" + MvcUriComponentsBuilder
409
.fromMethodCall(UserController.class, controller -> controller.getUser(savedUser.getId(), null))
410
.toUriString();
411
}
412
}
413
```
414
415
### Advice Interfaces
416
417
#### RequestBodyAdvice
418
419
Allows customizing the request before the body is read and converted.
420
421
```java { .api }
422
/**
423
* Allows customizing the request before the body is read and converted into an Object.
424
*/
425
public interface RequestBodyAdvice {
426
427
/** Return true if this advice applies to the given method parameter */
428
boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType);
429
430
/** Invoked first before the body is read */
431
HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException;
432
433
/** Invoked second after the body is converted to an Object */
434
Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType);
435
436
/** Invoked third if the body is empty */
437
Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType);
438
}
439
```
440
441
#### ResponseBodyAdvice
442
443
Allows customizing the response after the execution of an @ResponseBody or ResponseEntity controller method.
444
445
```java { .api }
446
/**
447
* Allows customizing the response after the execution of an @ResponseBody or ResponseEntity controller method.
448
*/
449
public interface ResponseBodyAdvice<T> {
450
451
/** Return true if this advice applies to the given controller method return type */
452
boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType);
453
454
/** Invoked after a HttpMessageConverter is selected and just before its write method is invoked */
455
T beforeBodyWrite(T body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response);
456
}
457
```
458
459
**Advice Usage Examples:**
460
461
```java
462
@ControllerAdvice
463
public class GlobalRequestResponseAdvice implements RequestBodyAdvice, ResponseBodyAdvice<Object> {
464
465
@Override
466
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
467
return true; // Apply to all @RequestBody parameters
468
}
469
470
@Override
471
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
472
// Log incoming request body
473
String body = StreamUtils.copyToString(inputMessage.getBody(), StandardCharsets.UTF_8);
474
log.info("Request body: {}", body);
475
return new HttpInputMessage() {
476
@Override
477
public InputStream getBody() throws IOException {
478
return new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8));
479
}
480
481
@Override
482
public HttpHeaders getHeaders() {
483
return inputMessage.getHeaders();
484
}
485
};
486
}
487
488
@Override
489
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
490
// Validate or transform the body
491
return body;
492
}
493
494
@Override
495
public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
496
return body;
497
}
498
499
@Override
500
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
501
return true; // Apply to all @ResponseBody methods
502
}
503
504
@Override
505
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
506
// Add common response headers or wrap response
507
response.getHeaders().add("X-Response-Time", String.valueOf(System.currentTimeMillis()));
508
509
if (body instanceof Map) {
510
Map<String, Object> wrapped = new HashMap<>((Map<String, Object>) body);
511
wrapped.put("timestamp", Instant.now());
512
return wrapped;
513
}
514
515
return body;
516
}
517
}
518
```