CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-springframework-boot--spring-boot-starter-webflux

Starter for building WebFlux applications using Spring Framework's Reactive Web support

Pending
Overview
Eval results
Files

annotation-controllers.mddocs/

Annotation-Based Controllers

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.

Controller Annotations

Class-Level Annotations

@RestController
public @interface RestController {
    @AliasFor(annotation = Controller.class)
    String value() default "";
}

@Controller
@Component
public @interface Controller {
    String value() default "";
}

Request Mapping Annotations

@RequestMapping
public @interface RequestMapping {
    String name() default "";
    String[] value() default {};
    String[] path() default {};
    RequestMethod[] method() default {};
    String[] params() default {};
    String[] headers() default {};
    String[] consumes() default {};
    String[] produces() default {};
}

@GetMapping
public @interface GetMapping {
    String name() default "";
    String[] value() default {};
    String[] path() default {};
    String[] params() default {};
    String[] headers() default {};
    String[] consumes() default {};
    String[] produces() default {};
}

@PostMapping
public @interface PostMapping { /* same structure as GetMapping */ }

@PutMapping
public @interface PutMapping { /* same structure as GetMapping */ }

@DeleteMapping
public @interface DeleteMapping { /* same structure as GetMapping */ }

@PatchMapping
public @interface PatchMapping { /* same structure as GetMapping */ }

Parameter Binding Annotations

Path Variables

@PathVariable
public @interface PathVariable {
    @AliasFor("name")
    String value() default "";
    String name() default "";
    boolean required() default true;
}

Request Parameters

@RequestParam
public @interface RequestParam {
    @AliasFor("name")
    String value() default "";
    String name() default "";
    boolean required() default true;
    String defaultValue() default ValueConstants.DEFAULT_NONE;
}

Request Body

@RequestBody
public @interface RequestBody {
    boolean required() default true;
}

Request Headers

@RequestHeader
public @interface RequestHeader {
    @AliasFor("name")
    String value() default "";
    String name() default "";
    boolean required() default true;
    String defaultValue() default ValueConstants.DEFAULT_NONE;
}

Cookie Values

@CookieValue
public @interface CookieValue {
    @AliasFor("name")
    String value() default "";
    String name() default "";
    boolean required() default true;
    String defaultValue() default ValueConstants.DEFAULT_NONE;
}

Response Handling

Response Annotations

@ResponseBody
public @interface ResponseBody {
}

@ResponseStatus
public @interface ResponseStatus {
    HttpStatus code() default HttpStatus.INTERNAL_SERVER_ERROR;
    @AliasFor("code")
    HttpStatus value() default HttpStatus.INTERNAL_SERVER_ERROR;
    String reason() default "";
}

Reactive Return Types

Single Values

// Reactor Mono for single values
public abstract class Mono<T> implements CorePublisher<T> {
    public static <T> Mono<T> just(T data);
    public static <T> Mono<T> empty();
    public static <T> Mono<T> error(Throwable error);
    public static <T> Mono<T> fromCallable(Callable<? extends T> supplier);
    public static <T> Mono<T> fromSupplier(Supplier<? extends T> supplier);
    
    public <R> Mono<R> map(Function<? super T, ? extends R> mapper);
    public <R> Mono<R> flatMap(Function<? super T, ? extends Mono<? extends R>> transformer);
    public Mono<T> filter(Predicate<? super T> tester);
    public Mono<T> switchIfEmpty(Mono<? extends T> alternate);
}

Multiple Values

// Reactor Flux for multiple values
public abstract class Flux<T> implements CorePublisher<T> {
    public static <T> Flux<T> just(T... data);
    public static <T> Flux<T> empty();
    public static <T> Flux<T> error(Throwable error);
    public static <T> Flux<T> fromIterable(Iterable<? extends T> it);
    public static <T> Flux<T> fromArray(T[] array);
    
    public <R> Flux<R> map(Function<? super T, ? extends R> mapper);
    public <R> Flux<R> flatMap(Function<? super T, ? extends Publisher<? extends R>> mapper);
    public Flux<T> filter(Predicate<? super T> predicate);
    public Flux<T> take(long n);
    public Flux<T> skip(long n);
    public Mono<List<T>> collectList();
}

Response Entity

public class ResponseEntity<T> extends HttpEntity<T> {
    public ResponseEntity(HttpStatus status);
    public ResponseEntity(T body, HttpStatus status);
    public ResponseEntity(MultiValueMap<String, String> headers, HttpStatus status);
    public ResponseEntity(T body, MultiValueMap<String, String> headers, HttpStatus status);
    
    public HttpStatus getStatusCode();
    
    public static BodyBuilder status(HttpStatus status);
    public static <T> ResponseEntity<T> ok(T body);
    public static <T> ResponseEntity<T> created(URI location);
    public static <T> ResponseEntity<T> notFound();
    public static <T> ResponseEntity<T> badRequest();
}

Usage Examples

Basic CRUD Controller

@RestController
@RequestMapping("/api/users")
public class UserController {
    
    private final UserService userService;
    
    public UserController(UserService userService) {
        this.userService = userService;
    }
    
    @GetMapping("/{id}")
    public Mono<ResponseEntity<User>> getUser(@PathVariable String id) {
        return userService.findById(id)
            .map(ResponseEntity::ok)
            .defaultIfEmpty(ResponseEntity.notFound().build());
    }
    
    @GetMapping
    public Flux<User> getAllUsers(
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "10") int size) {
        return userService.findAll(page, size);
    }
    
    @PostMapping
    public Mono<ResponseEntity<User>> createUser(@RequestBody Mono<User> userMono) {
        return userMono
            .flatMap(userService::save)
            .map(user -> ResponseEntity.created(URI.create("/api/users/" + user.getId())).body(user));
    }
    
    @PutMapping("/{id}")
    public Mono<ResponseEntity<User>> updateUser(
            @PathVariable String id, 
            @RequestBody Mono<User> userMono) {
        return userMono
            .flatMap(user -> userService.update(id, user))
            .map(ResponseEntity::ok)
            .defaultIfEmpty(ResponseEntity.notFound().build());
    }
    
    @DeleteMapping("/{id}")
    public Mono<ResponseEntity<Void>> deleteUser(@PathVariable String id) {
        return userService.deleteById(id)
            .then(Mono.just(ResponseEntity.noContent().<Void>build()));
    }
}

Stream Processing Controller

@RestController
@RequestMapping("/api/data")
public class DataStreamController {
    
    @GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> streamData() {
        return Flux.interval(Duration.ofSeconds(1))
            .map(i -> "Data point: " + i)
            .take(10);
    }
    
    @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public Mono<String> uploadFile(@RequestPart("file") Mono<FilePart> fileMono) {
        return fileMono
            .flatMap(file -> file.transferTo(Paths.get("/tmp/" + file.filename())))
            .then(Mono.just("File uploaded successfully"));
    }
    
    @GetMapping("/search")
    public Flux<SearchResult> search(
            @RequestParam String query,
            @RequestHeader(name = "Accept-Language", defaultValue = "en") String language) {
        return searchService.search(query, language);
    }
}

Exception Handling

@RestController
public class UserController {
    
    @GetMapping("/users/{id}")
    public Mono<User> getUser(@PathVariable String id) {
        return userService.findById(id)
            .switchIfEmpty(Mono.error(new UserNotFoundException("User not found: " + id)));
    }
    
    @ExceptionHandler(UserNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public Mono<ErrorResponse> handleUserNotFound(UserNotFoundException ex) {
        return Mono.just(new ErrorResponse("USER_NOT_FOUND", ex.getMessage()));
    }
    
    @ExceptionHandler(ValidationException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Mono<ErrorResponse> handleValidation(ValidationException ex) {
        return Mono.just(new ErrorResponse("VALIDATION_ERROR", ex.getMessage()));
    }
}

Custom Content Types

@RestController
public class MediaController {
    
    @GetMapping(value = "/xml-data", produces = MediaType.APPLICATION_XML_VALUE)
    public Mono<XmlData> getXmlData() {
        return dataService.getXmlData();
    }
    
    @PostMapping(value = "/json-data", 
                 consumes = MediaType.APPLICATION_JSON_VALUE,
                 produces = MediaType.APPLICATION_JSON_VALUE)
    public Mono<JsonResponse> processJsonData(@RequestBody Mono<JsonRequest> request) {
        return request.flatMap(dataService::processJson);
    }
    
    @GetMapping(value = "/csv-export", produces = "text/csv")
    public Flux<String> exportCsv() {
        return dataService.getAllRecords()
            .map(record -> String.join(",", record.getFields()));
    }
}

Integration with Validation

@RestController
@Validated
public class ValidatedController {
    
    @PostMapping("/users")
    public Mono<User> createUser(@Valid @RequestBody Mono<User> userMono) {
        return userMono.flatMap(userService::save);
    }
    
    @GetMapping("/users/{id}")
    public Mono<User> getUser(@PathVariable @Pattern(regexp = "^[0-9]+$") String id) {
        return userService.findById(id);
    }
    
    @GetMapping("/users")
    public Flux<User> getUsers(
            @RequestParam @Min(0) int page,
            @RequestParam @Min(1) @Max(100) int size) {
        return userService.findAll(page, size);
    }
}

Request Processing Features

Content Negotiation

Spring WebFlux automatically handles content negotiation based on Accept headers and produces/consumes attributes in mapping annotations.

Asynchronous Request Processing

All controller methods returning reactive types are processed asynchronously, allowing the server to handle thousands of concurrent requests with minimal thread usage.

Backpressure Support

When using Flux return types, Spring WebFlux automatically applies backpressure, ensuring that slow consumers don't overwhelm the system.

Install with Tessl CLI

npx tessl i tessl/maven-org-springframework-boot--spring-boot-starter-webflux

docs

annotation-controllers.md

configuration.md

error-handling.md

functional-routing.md

index.md

server-configuration.md

testing.md

webclient.md

tile.json