or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

annotation-controllers.mdconfiguration.mdcontent-negotiation.mdexception-handling.mdfunctional-routing.mdindex.mdresource-handling.mdview-rendering.mdwebclient.mdwebsocket.md
tile.json

annotation-controllers.mddocs/

Annotation-Based Controllers

Spring WebFlux supports annotation-based controllers using @RestController and @RequestMapping annotations. This programming model is similar to Spring MVC and provides a familiar approach for handling HTTP requests with annotated methods, argument resolvers, and result handlers.

Capabilities

Define Controllers

Use @RestController to mark a class as a REST controller where method return values are automatically written to the response body.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
    @AliasFor(annotation = Controller.class)
    String value() default "";
}

Usage:

import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import reactor.core.publisher.Mono;
import reactor.core.publisher.Flux;

@RestController
@RequestMapping("/users")
public class UserController {

    private final UserRepository repository;

    public UserController(UserRepository repository) {
        this.repository = repository;
    }

    @GetMapping("/{id}")
    public Mono<User> getUser(@PathVariable String id) {
        return repository.findById(id);
    }

    @GetMapping
    public Flux<User> listUsers() {
        return repository.findAll();
    }

    @PostMapping
    public Mono<User> createUser(@RequestBody User user) {
        return repository.save(user);
    }
}

Request Mapping

The @RequestMapping annotation maps HTTP requests to handler methods.

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
@Reflective(ControllerMappingReflectiveProcessor.class)
public @interface RequestMapping {
    // Name for mapping
    String name() default "";

    // Path patterns
    @AliasFor("path")
    String[] value() default {};

    @AliasFor("value")
    String[] path() default {};

    // HTTP methods
    RequestMethod[] method() default {};

    // Request parameters
    String[] params() default {};

    // Request headers
    String[] headers() default {};

    // Consumable media types
    String[] consumes() default {};

    // Producible media types
    String[] produces() default {};

    // API version (since Spring 7.0)
    String version() default "";
}

HTTP method-specific shortcuts:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.GET)
public @interface GetMapping {
    String name() default "";
    String[] value() default {};
    String[] path() default {};
    String[] params() default {};
    String[] headers() default {};
    String[] consumes() default {};
    String[] produces() default {};
    String version() default "";
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.POST)
public @interface PostMapping {
    String name() default "";
    String[] value() default {};
    String[] path() default {};
    String[] params() default {};
    String[] headers() default {};
    String[] consumes() default {};
    String[] produces() default {};
    String version() default "";
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.PUT)
public @interface PutMapping {
    String name() default "";
    String[] value() default {};
    String[] path() default {};
    String[] params() default {};
    String[] headers() default {};
    String[] consumes() default {};
    String[] produces() default {};
    String version() default "";
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.PATCH)
public @interface PatchMapping {
    String name() default "";
    String[] value() default {};
    String[] path() default {};
    String[] params() default {};
    String[] headers() default {};
    String[] consumes() default {};
    String[] produces() default {};
    String version() default "";
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.DELETE)
public @interface DeleteMapping {
    String name() default "";
    String[] value() default {};
    String[] path() default {};
    String[] params() default {};
    String[] headers() default {};
    String[] consumes() default {};
    String[] produces() default {};
    String version() default "";
}

Usage:

@RestController
@RequestMapping("/api/products")
public class ProductController {

    // Match GET /api/products
    @GetMapping
    public Flux<Product> listProducts() {
        return productService.findAll();
    }

    // Match GET /api/products/{id}
    @GetMapping("/{id}")
    public Mono<Product> getProduct(@PathVariable Long id) {
        return productService.findById(id);
    }

    // Match POST /api/products with JSON content
    @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
    public Mono<Product> createProduct(@RequestBody Product product) {
        return productService.save(product);
    }

    // Match PUT /api/products/{id}
    @PutMapping("/{id}")
    public Mono<Product> updateProduct(@PathVariable Long id, @RequestBody Product product) {
        return productService.update(id, product);
    }

    // Match DELETE /api/products/{id}
    @DeleteMapping("/{id}")
    public Mono<Void> deleteProduct(@PathVariable Long id) {
        return productService.deleteById(id);
    }

    // Advanced matching with params and headers
    @GetMapping(params = "active=true", headers = "X-API-Version=1")
    public Flux<Product> getActiveProducts() {
        return productService.findActive();
    }
}

Request Mapping Handler Mapping

The RequestMappingHandlerMapping class creates request mappings from @RequestMapping annotations.

public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping
        implements MatchableHandlerMapping, EmbeddedValueResolverAware {

    // Configure path prefixes for controller types
    public void setPathPrefixes(Map<String, Predicate<Class<?>>> prefixes) { ... }

    // Get path prefixes
    public Map<String, Predicate<Class<?>>> getPathPrefixes() { ... }

    // Set content type resolver
    public void setContentTypeResolver(RequestedContentTypeResolver contentTypeResolver) { ... }

    // Get content type resolver
    public RequestedContentTypeResolver getContentTypeResolver() { ... }

    // Set embedded value resolver
    @Override
    public void setEmbeddedValueResolver(StringValueResolver resolver) { ... }

    // Create mapping from @RequestMapping annotation
    @Override
    protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) { ... }

    // Additional configuration methods...
}

Request Mapping Handler Adapter

The RequestMappingHandlerAdapter class invokes annotated controller methods and handles their arguments and return values.

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
        implements BeanFactoryAware, InitializingBean {

    // Configure message readers for request body
    public void setMessageReaders(List<HttpMessageReader<?>> messageReaders) { ... }

    // Get configured message readers
    public List<HttpMessageReader<?>> getMessageReaders() { ... }

    // Configure argument resolvers
    public void setArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { ... }

    // Configure custom argument resolvers
    public void setCustomArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { ... }

    // Get configured argument resolvers
    public List<HandlerMethodArgumentResolver> getArgumentResolvers() { ... }

    // Configure initializer methods
    public void setInitBinderMethods(List<InvocableHandlerMethod> initBinderMethods) { ... }

    // Configure model attribute methods
    public void setModelAttributeMethods(List<InvocableHandlerMethod> modelAttributeMethods) { ... }

    // Configure reactive adapter registry
    public void setReactiveAdapterRegistry(ReactiveAdapterRegistry reactiveAdapterRegistry) { ... }

    // Get reactive adapter registry
    public ReactiveAdapterRegistry getReactiveAdapterRegistry() { ... }

    // Configure blocking execution
    public void setBlockingExecutionScheduler(Scheduler scheduler) { ... }

    // Configure web binding initializer
    public void setWebBindingInitializer(WebBindingInitializer webBindingInitializer) { ... }

    // Get web binding initializer
    public WebBindingInitializer getWebBindingInitializer() { ... }

    @Override
    public boolean supports(Object handler) { ... }

    @Override
    public Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler) { ... }
}

Method Argument Resolvers

Argument resolver annotations for controller method parameters:

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
    @AliasFor("name")
    String value() default "";

    @AliasFor("value")
    String name() default "";

    boolean required() default true;

    String defaultValue() default ValueConstants.DEFAULT_NONE;
}
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PathVariable {
    @AliasFor("name")
    String value() default "";

    @AliasFor("value")
    String name() default "";

    boolean required() default true;
}
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestHeader {
    @AliasFor("name")
    String value() default "";

    @AliasFor("value")
    String name() default "";

    boolean required() default true;

    String defaultValue() default ValueConstants.DEFAULT_NONE;
}
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CookieValue {
    @AliasFor("name")
    String value() default "";

    @AliasFor("value")
    String name() default "";

    boolean required() default true;

    String defaultValue() default ValueConstants.DEFAULT_NONE;
}
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestBody {
    boolean required() default true;
}
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestPart {
    @AliasFor("name")
    String value() default "";

    @AliasFor("value")
    String name() default "";

    boolean required() default true;
}
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ModelAttribute {
    @AliasFor("name")
    String value() default "";

    @AliasFor("value")
    String name() default "";

    boolean binding() default true;
}
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SessionAttribute {
    @AliasFor("name")
    String value() default "";

    @AliasFor("value")
    String name() default "";

    boolean required() default true;
}
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestAttribute {
    @AliasFor("name")
    String value() default "";

    @AliasFor("value")
    String name() default "";

    boolean required() default true;
}
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MatrixVariable {
    @AliasFor("name")
    String value() default "";

    @AliasFor("value")
    String name() default "";

    String pathVar() default ValueConstants.DEFAULT_NONE;

    boolean required() default true;

    String defaultValue() default ValueConstants.DEFAULT_NONE;
}

Usage:

@RestController
@RequestMapping("/api")
public class ExampleController {

    // Path variable
    @GetMapping("/users/{id}")
    public Mono<User> getUser(@PathVariable Long id) {
        return userService.findById(id);
    }

    // Request parameter
    @GetMapping("/search")
    public Flux<User> search(@RequestParam String query,
                             @RequestParam(defaultValue = "0") int page,
                             @RequestParam(defaultValue = "20") int size) {
        return userService.search(query, page, size);
    }

    // Request header
    @GetMapping("/data")
    public Mono<String> getData(@RequestHeader("Authorization") String authHeader) {
        return dataService.fetchData(authHeader);
    }

    // Cookie value
    @GetMapping("/profile")
    public Mono<User> getProfile(@CookieValue("sessionId") String sessionId) {
        return userService.findBySessionId(sessionId);
    }

    // Request body
    @PostMapping("/users")
    public Mono<User> createUser(@RequestBody User user) {
        return userService.save(user);
    }

    // Matrix variable
    @GetMapping("/products/{category}")
    public Flux<Product> getProducts(@PathVariable String category,
                                     @MatrixVariable(pathVar = "category") List<String> tags) {
        return productService.findByCategoryAndTags(category, tags);
    }

    // Multiple parts
    @PostMapping("/upload")
    public Mono<String> handleUpload(@RequestPart("file") FilePart file,
                                     @RequestPart("metadata") Metadata metadata) {
        return fileService.save(file, metadata);
    }

    // Session attribute
    @GetMapping("/cart")
    public Mono<Cart> getCart(@SessionAttribute("cart") Cart cart) {
        return Mono.just(cart);
    }

    // Request attribute
    @GetMapping("/internal")
    public Mono<String> getInternal(@RequestAttribute("userId") String userId) {
        return dataService.getInternalData(userId);
    }

    // Model attribute
    @GetMapping("/form")
    public Mono<String> showForm(@ModelAttribute("user") User user) {
        return Mono.just("formView");
    }

    // ServerWebExchange
    @GetMapping("/exchange")
    public Mono<String> handleExchange(ServerWebExchange exchange) {
        return Mono.just(exchange.getRequest().getPath().value());
    }

    // Principal
    @GetMapping("/me")
    public Mono<User> getCurrentUser(Principal principal) {
        return userService.findByUsername(principal.getName());
    }

    // Multiple reactive types
    @GetMapping("/stream")
    public Flux<Event> streamEvents() {
        return eventService.streamEvents();
    }

    @GetMapping("/single")
    public Mono<Result> getSingleResult() {
        return resultService.getResult();
    }
}

Argument Resolver Interfaces

public interface HandlerMethodArgumentResolver {
    // Check if this resolver supports the parameter
    boolean supportsParameter(MethodParameter parameter);

    // Resolve the argument value
    Mono<Object> resolveArgument(MethodParameter parameter,
                                 BindingContext bindingContext,
                                 ServerWebExchange exchange);
}
public interface SyncHandlerMethodArgumentResolver {
    // Check if this resolver supports the parameter
    boolean supportsParameter(MethodParameter parameter);

    // Resolve the argument value synchronously
    Object resolveArgumentValue(MethodParameter parameter,
                                BindingContext bindingContext,
                                ServerWebExchange exchange);
}

Argument Resolver Configuration

public class ArgumentResolverConfigurer {
    // Add custom argument resolvers before built-in resolvers
    public ArgumentResolverConfigurer addCustomResolver(HandlerMethodArgumentResolver... resolvers) { ... }

    // Get custom resolvers
    public List<HandlerMethodArgumentResolver> getCustomResolvers() { ... }
}

Result Handlers

Handler result handlers for different return value types:

public class ResponseEntityResultHandler extends AbstractMessageWriterResultHandler
        implements HandlerResultHandler {

    public ResponseEntityResultHandler(List<HttpMessageWriter<?>> writers,
                                      RequestedContentTypeResolver resolver) { ... }

    public ResponseEntityResultHandler(List<HttpMessageWriter<?>> writers,
                                      RequestedContentTypeResolver resolver,
                                      ReactiveAdapterRegistry registry) { ... }

    @Override
    public boolean supports(HandlerResult result) { ... }

    @Override
    public Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result) { ... }
}
public class ResponseBodyResultHandler extends AbstractMessageWriterResultHandler
        implements HandlerResultHandler {

    public ResponseBodyResultHandler(List<HttpMessageWriter<?>> writers,
                                    RequestedContentTypeResolver resolver) { ... }

    public ResponseBodyResultHandler(List<HttpMessageWriter<?>> writers,
                                    RequestedContentTypeResolver resolver,
                                    ReactiveAdapterRegistry registry) { ... }

    @Override
    public boolean supports(HandlerResult result) { ... }

    @Override
    public Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result) { ... }
}
public class ViewResolutionResultHandler extends HandlerResultHandlerSupport
        implements HandlerResultHandler, Ordered {

    public ViewResolutionResultHandler(List<ViewResolver> viewResolvers,
                                      RequestedContentTypeResolver contentTypeResolver) { ... }

    public ViewResolutionResultHandler(List<ViewResolver> viewResolvers,
                                      RequestedContentTypeResolver contentTypeResolver,
                                      ReactiveAdapterRegistry adapterRegistry) { ... }

    // Set default view names
    public void setDefaultViews(List<View> defaultViews) { ... }

    // Get default views
    public List<View> getDefaultViews() { ... }

    @Override
    public boolean supports(HandlerResult result) { ... }

    @Override
    public Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result) { ... }
}

Response Body

The @ResponseBody annotation indicates that the return value should be written to the response body.

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseBody {
}

Response Status

The @ResponseStatus annotation marks a method or exception class with a status code and reason.

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseStatus {
    @AliasFor("code")
    HttpStatus value() default HttpStatus.INTERNAL_SERVER_ERROR;

    @AliasFor("value")
    HttpStatus code() default HttpStatus.INTERNAL_SERVER_ERROR;

    String reason() default "";
}

Usage:

@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
    public ResourceNotFoundException(String message) {
        super(message);
    }
}

@RestController
public class UserController {

    @GetMapping("/users/{id}")
    public Mono<User> getUser(@PathVariable Long id) {
        return userService.findById(id)
            .switchIfEmpty(Mono.error(new ResourceNotFoundException("User not found")));
    }

    @DeleteMapping("/users/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public Mono<Void> deleteUser(@PathVariable Long id) {
        return userService.deleteById(id);
    }
}

Types

Request Mapping Info

public final class RequestMappingInfo implements RequestCondition<RequestMappingInfo> {
    // Get patterns
    public PatternsRequestCondition getPatternsCondition() { ... }

    // Get methods
    public RequestMethodsRequestCondition getMethodsCondition() { ... }

    // Get params
    public ParamsRequestCondition getParamsCondition() { ... }

    // Get headers
    public HeadersRequestCondition getHeadersCondition() { ... }

    // Get consumes
    public ConsumesRequestCondition getConsumesCondition() { ... }

    // Get produces
    public ProducesRequestCondition getProducesCondition() { ... }

    // Get custom condition
    public RequestConditionHolder getCustomCondition() { ... }

    // Get API version condition
    public VersionRequestCondition getVersionCondition() { ... }

    @Override
    public RequestMappingInfo combine(RequestMappingInfo other) { ... }

    @Override
    public RequestMappingInfo getMatchingCondition(ServerWebExchange exchange) { ... }

    @Override
    public int compareTo(RequestMappingInfo other, ServerWebExchange exchange) { ... }

    // Builder for RequestMappingInfo
    public static Builder paths(String... paths) { ... }

    public static class Builder {
        public Builder paths(String... paths) { ... }
        public Builder methods(RequestMethod... methods) { ... }
        public Builder params(String... params) { ... }
        public Builder headers(String... headers) { ... }
        public Builder consumes(String... consumes) { ... }
        public Builder produces(String... produces) { ... }
        public Builder mappingName(String name) { ... }
        public Builder customCondition(RequestCondition<?> condition) { ... }
        public Builder version(String version) { ... }
        public Builder options(BuilderConfiguration options) { ... }
        public RequestMappingInfo build() { ... }
    }

    public static class BuilderConfiguration {
        public void setPathPatternParser(PathPatternParser parser) { ... }
        public PathPatternParser getPathPatternParser() { ... }
        public void setContentTypeResolver(RequestedContentTypeResolver resolver) { ... }
        public RequestedContentTypeResolver getContentTypeResolver() { ... }
    }
}

Invocable Handler Method

public class InvocableHandlerMethod extends HandlerMethod {
    public InvocableHandlerMethod(Object bean, Method method) { ... }
    public InvocableHandlerMethod(HandlerMethod handlerMethod) { ... }

    // Set argument resolvers
    public void setArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { ... }

    // Set reactive adapter registry
    public void setReactiveAdapterRegistry(ReactiveAdapterRegistry registry) { ... }

    // Invoke the method
    public Mono<HandlerResult> invoke(ServerWebExchange exchange, BindingContext bindingContext, Object... providedArgs) { ... }
}

Synchronous Invocable Handler Method

public class SyncInvocableHandlerMethod extends HandlerMethod {
    public SyncInvocableHandlerMethod(Object bean, Method method) { ... }
    public SyncInvocableHandlerMethod(HandlerMethod handlerMethod) { ... }

    // Set argument resolvers
    public void setArgumentResolvers(List<SyncHandlerMethodArgumentResolver> resolvers) { ... }

    // Invoke the method synchronously
    public Object invokeForValue(ServerWebExchange exchange, BindingContext bindingContext, Object... providedArgs) { ... }
}

Handler Method Argument Resolver Composite

public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {
    // Add resolver
    public HandlerMethodArgumentResolverComposite addResolver(HandlerMethodArgumentResolver resolver) { ... }

    // Add resolvers
    public HandlerMethodArgumentResolverComposite addResolvers(HandlerMethodArgumentResolver... resolvers) { ... }
    public HandlerMethodArgumentResolverComposite addResolvers(List<? extends HandlerMethodArgumentResolver> resolvers) { ... }

    // Get resolvers
    public List<HandlerMethodArgumentResolver> getResolvers() { ... }

    // Clear resolvers
    public void clear() { ... }

    @Override
    public boolean supportsParameter(MethodParameter parameter) { ... }

    @Override
    public Mono<Object> resolveArgument(MethodParameter parameter,
                                        BindingContext bindingContext,
                                        ServerWebExchange exchange) { ... }
}

Abstract Handler Method Mapping

public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping
        implements InitializingBean {

    // Register handler method
    protected void registerHandlerMethod(Object handler, Method method, T mapping) { ... }

    // Create handler method
    protected HandlerMethod createHandlerMethod(Object handler, Method method) { ... }

    // Get handler internal (abstract)
    protected abstract T getMappingForMethod(Method method, Class<?> handlerType);

    // Get matching mapping (abstract)
    protected abstract T getMatchingMapping(T mapping, ServerWebExchange exchange);

    // Compare mappings (abstract)
    protected abstract Comparator<T> getMappingComparator(ServerWebExchange exchange);
}

Request Mapping Info Handler Mapping

public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMethodMapping<RequestMappingInfo> {

    @Override
    protected RequestMappingInfo getMatchingMapping(RequestMappingInfo info, ServerWebExchange exchange) { ... }

    @Override
    protected Comparator<RequestMappingInfo> getMappingComparator(ServerWebExchange exchange) { ... }

    // Handle match
    protected void handleMatch(RequestMappingInfo info, String lookupPath, ServerWebExchange exchange) { ... }

    // Handle no match
    protected HandlerMethod handleNoMatch(Set<RequestMappingInfo> infos, String lookupPath, ServerWebExchange exchange) { ... }
}

Types

Data Binding

InitBinder Annotation

The @InitBinder annotation identifies methods that customize data binding for controller methods.

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface InitBinder {
    // Names of command/form attributes this init-binder applies to
    // Default: applies to all
    String[] value() default {};
}

Usage:

@RestController
@RequestMapping("/users")
public class UserController {

    @InitBinder
    public void initBinder(WebExchangeDataBinder binder) {
        // Customize data binding
        binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
        binder.setDisallowedFields("id"); // Security: prevent binding to id field
    }

    @InitBinder("user")
    public void initUserBinder(WebExchangeDataBinder binder) {
        // Only applies to @ModelAttribute("user")
        binder.addValidators(new UserValidator());
    }

    @PostMapping
    public Mono<User> createUser(@ModelAttribute User user) {
        // Data binding configured by @InitBinder methods
        return userService.save(user);
    }
}

WebExchangeDataBinder

Data binder for binding request data to Java objects in reactive web applications.

public class WebExchangeDataBinder extends WebDataBinder {
    public WebExchangeDataBinder(Object target) { ... }
    public WebExchangeDataBinder(Object target, String objectName) { ... }

    // Constructor-based binding (since Spring 6.1)
    public Mono<Void> construct(ServerWebExchange exchange) { ... }

    // Bind query params, form data, or multipart data
    public Mono<Void> bind(ServerWebExchange exchange) { ... }

    // Get values to bind from the exchange
    protected Mono<Map<String, Object>> getValuesToBind(ServerWebExchange exchange) { ... }
}

Multipart Types

Part Interface

Base interface for parts in multipart requests.

public interface Part {
    // Get part name
    String name();

    // Get part headers
    HttpHeaders headers();

    // Get part content as DataBuffer stream
    Flux<DataBuffer> content();

    // Delete underlying storage
    default Mono<Void> delete() { ... }
}

Usage:

@PostMapping("/upload")
public Mono<String> handleUpload(@RequestPart("file") Part filePart) {
    String filename = filePart.headers().getContentDisposition().getFilename();
    Flux<DataBuffer> content = filePart.content();

    return DataBufferUtils.write(content, Path.of("/uploads/" + filename))
        .then(filePart.delete())
        .thenReturn("File uploaded: " + filename);
}

FilePart Interface

Specialized Part for file uploads.

public interface FilePart extends Part {
    // Get the original filename
    String filename();

    // Transfer the file to a destination
    Mono<Void> transferTo(Path dest);
    Mono<Void> transferTo(File dest);
}

Usage:

@PostMapping("/upload")
public Mono<String> handleFileUpload(@RequestPart("file") FilePart filePart) {
    String filename = filePart.filename();
    Path destination = Path.of("/uploads/" + filename);

    return filePart.transferTo(destination)
        .thenReturn("File uploaded successfully: " + filename);
}

FormFieldPart Interface

Specialized Part for form fields.

public interface FormFieldPart extends Part {
    // Get the form field value as String
    String value();
}

Usage:

@PostMapping("/submit")
public Mono<String> handleFormSubmit(@RequestPart("username") FormFieldPart usernamePart,
                                     @RequestPart("email") FormFieldPart emailPart) {
    String username = usernamePart.value();
    String email = emailPart.value();

    return userService.register(username, email)
        .thenReturn("User registered");
}