or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

annotation-controllers.mdconfiguration.mderror-handling.mdfunctional-routing.mdindex.mdserver-configuration.mdtesting.mdwebclient.md

annotation-controllers.mddocs/

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.