or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

configuration.mdcontroller-annotations.mdfunctional-web.mdindex.mdresource-handling.mdservlet-framework.mdsupport-utilities.mdview-resolution.md

controller-annotations.mddocs/

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

```