CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-micronaut

Modern, JVM-based framework for building modular, easily testable microservice and serverless applications with compile-time DI and fast startup.

Pending
Overview
Eval results
Files

http-server.mddocs/

HTTP Server

Micronaut's HTTP server provides high-performance request handling with both blocking and non-blocking APIs, comprehensive routing, content negotiation, and error handling.

Capabilities

Controllers

Define HTTP endpoints using controller classes with declarative routing annotations.

/**
 * Basic controller with GET endpoint
 */
@Controller("/api/users")
public class UserController {
    
    @Get("/{id}")
    public User getUser(Long id) {
        return userService.findById(id);
    }
    
    @Get
    public List<User> getUsers(@QueryValue Optional<String> filter) {
        return userService.findAll(filter.orElse(null));
    }
    
    @Post
    public HttpResponse<User> createUser(@Body @Valid User user) {
        User created = userService.save(user);
        return HttpResponse.created(created);
    }
    
    @Put("/{id}")
    public User updateUser(Long id, @Body @Valid User user) {
        return userService.update(id, user);
    }
    
    @Delete("/{id}")
    @Status(HttpStatus.NO_CONTENT)
    public void deleteUser(Long id) {
        userService.delete(id);
    }
}

Parameter Binding

Bind HTTP request data to method parameters with type conversion and validation.

/**
 * Path parameters, query parameters, and headers
 */
@Controller("/api/products")
public class ProductController {
    
    @Get("/{id}")
    public Product getProduct(@PathVariable Long id,
                             @QueryValue Optional<Boolean> includeDetails,
                             @Header("Accept-Language") String language) {
        return productService.findById(id, includeDetails.orElse(false), language);
    }
    
    @Get
    public Page<Product> searchProducts(@QueryValue String q,
                                       @QueryValue @Positive int page,
                                       @QueryValue @Positive int size,
                                       @QueryValue Optional<String> sort) {
        return productService.search(q, page, size, sort.orElse("name"));
    }
    
    @Post("/bulk")
    public List<Product> createProducts(@Body List<@Valid Product> products,
                                       @Header("X-Batch-Id") String batchId) {
        return productService.createBatch(products, batchId);
    }
}

/**
 * Form data and multipart handling
 */
@Controller("/upload")
public class FileUploadController {
    
    @Post(value = "/", consumes = MediaType.MULTIPART_FORM_DATA)
    public HttpResponse<String> upload(@Part CompletedFileUpload file,
                                      @Part("description") String description) {
        String filename = fileService.store(file, description);
        return HttpResponse.ok("File uploaded: " + filename);
    }
    
    @Post(value = "/form", consumes = MediaType.APPLICATION_FORM_URLENCODED)
    public HttpResponse<String> handleForm(@Body Map<String, String> formData) {
        return HttpResponse.ok("Form processed");
    }
}

Content Negotiation

Handle different content types for request and response bodies.

/**
 * Content type handling
 */
@Controller("/api/data")
public class DataController {
    
    @Get(value = "/export", produces = {
        MediaType.APPLICATION_JSON,
        MediaType.APPLICATION_XML,
        "text/csv"
    })
    public Object exportData(@Header("Accept") String acceptHeader) {
        if (acceptHeader.contains("xml")) {
            return new XmlDataResponse();
        } else if (acceptHeader.contains("csv")) {
            return new CsvDataResponse();
        }
        return new JsonDataResponse();
    }
    
    @Post(value = "/import", consumes = {
        MediaType.APPLICATION_JSON,
        MediaType.APPLICATION_XML
    })
    public HttpResponse<ImportResult> importData(@Body Object data) {
        ImportResult result = dataService.importData(data);
        return HttpResponse.ok(result);
    }
}

Reactive Controllers

Handle requests reactively using reactive types for non-blocking I/O.

/**
 * Reactive endpoints with Single, Maybe, and Flowable
 */
@Controller("/api/async")
public class AsyncController {
    
    @Get("/user/{id}")
    public Single<User> getUserAsync(Long id) {
        return userService.findByIdAsync(id);
    }
    
    @Get("/users")
    public Flowable<User> streamUsers(@QueryValue Optional<String> filter) {
        return userService.streamAll(filter.orElse(null));
    }
    
    @Post("/user")
    public Single<HttpResponse<User>> createUserAsync(@Body @Valid User user) {
        return userService.saveAsync(user)
            .map(HttpResponse::created);
    }
    
    @Get(value = "/events", produces = MediaType.TEXT_EVENT_STREAM)
    public Publisher<Event> streamEvents() {
        return eventService.streamEvents();
    }
}

/**
 * CompletableFuture support
 */
@Controller("/api/future")
public class FutureController {
    
    @Get("/data/{id}")
    public CompletableFuture<Data> getDataAsync(Long id) {
        return dataService.fetchAsync(id);
    }
}

Error Handling

Handle errors and exceptions with custom error responses.

/**
 * Exception handlers
 */
@Singleton
public class GlobalExceptionHandler implements ExceptionHandler<Exception, HttpResponse<?>> {
    
    @Override
    public HttpResponse<?> handle(HttpRequest request, Exception exception) {
        return HttpResponse.serverError()
            .body(Map.of("error", exception.getMessage()));
    }
}

/**
 * Specific exception handlers
 */
@Singleton
public class ValidationExceptionHandler 
    implements ExceptionHandler<ConstraintViolationException, HttpResponse<?>> {
    
    @Override
    public HttpResponse<?> handle(HttpRequest request, 
                                 ConstraintViolationException exception) {
        List<String> errors = exception.getConstraintViolations()
            .stream()
            .map(ConstraintViolation::getMessage)
            .collect(Collectors.toList());
            
        return HttpResponse.badRequest()
            .body(Map.of("errors", errors));
    }
}

/**
 * Controller-level error handling
 */
@Controller("/api/orders")
public class OrderController {
    
    @Get("/{id}")
    public Order getOrder(Long id) {
        return orderService.findById(id);
    }
    
    @Error(exception = OrderNotFoundException.class)
    public HttpResponse<Map<String, String>> handleOrderNotFound(OrderNotFoundException ex) {
        return HttpResponse.notFound(Map.of("error", ex.getMessage()));
    }
    
    @Error(status = HttpStatus.BAD_REQUEST)
    public HttpResponse<Map<String, String>> handleBadRequest() {
        return HttpResponse.badRequest(Map.of("error", "Invalid request"));
    }
}

Filters

Implement request/response filtering for cross-cutting concerns.

/**
 * HTTP filters for cross-cutting concerns
 */
@Filter("/api/**")
public class AuthenticationFilter implements HttpServerFilter {
    
    @Override
    public Publisher<MutableHttpResponse<?>> doFilter(HttpRequest<?> request,
                                                     ServerFilterChain chain) {
        String authHeader = request.getHeaders().get("Authorization");
        if (authHeader == null || !isValidToken(authHeader)) {
            MutableHttpResponse<?> response = HttpResponse.unauthorized();
            return Publishers.just(response);
        }
        return chain.proceed(request);
    }
    
    private boolean isValidToken(String token) {
        // Token validation logic
        return true;
    }
}

/**
 * CORS filter
 */
@Filter("/**")
public class CorsFilter implements HttpServerFilter {
    
    @Override
    public Publisher<MutableHttpResponse<?>> doFilter(HttpRequest<?> request,
                                                     ServerFilterChain chain) {
        if (request.getMethod() == HttpMethod.OPTIONS) {
            MutableHttpResponse<?> response = HttpResponse.ok()
                .header("Access-Control-Allow-Origin", "*")
                .header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
                .header("Access-Control-Allow-Headers", "Content-Type, Authorization");
            return Publishers.just(response);
        }
        
        return chain.proceed(request)
            .map(response -> response.header("Access-Control-Allow-Origin", "*"));
    }
}

Server Configuration

Configure the HTTP server with custom settings and SSL support.

/**
 * Server configuration properties
 */
@ConfigurationProperties("micronaut.server")
public class ServerConfiguration {
    private int port = 8080;
    private String host = "localhost";
    private Duration readTimeout = Duration.ofSeconds(30);
    private Duration writeTimeout = Duration.ofSeconds(30);
    private int maxRequestSize = 1024 * 1024 * 10; // 10MB
    
    // getters and setters
}

/**
 * SSL configuration
 */
@ConfigurationProperties("micronaut.server.ssl")
public class SslConfiguration {
    private boolean enabled = false;
    private String keyStore;
    private String keyStorePassword;
    private String keyStoreType = "JKS";
    private String trustStore;
    private String trustStorePassword;
    
    // getters and setters
}

/**
 * Custom server configuration
 */
@Factory
public class ServerFactory {
    
    @Bean
    @Replaces(NettyHttpServerConfiguration.class)
    public NettyHttpServerConfiguration customServerConfig() {
        return new NettyHttpServerConfiguration() {
            @Override
            public int getMaxHeaderSize() {
                return 16384; // 16KB headers
            }
        };
    }
}

Types

// Core HTTP types
public interface HttpRequest<B> extends HttpMessage<B> {
    HttpMethod getMethod();
    URI getUri();
    String getPath();
    HttpParameters getParameters();
    Map<String, Object> getAttributes();
    Optional<B> getBody();
    <T> Optional<T> getBody(Class<T> type);
}

public interface HttpResponse<B> extends HttpMessage<B> {
    HttpStatus getStatus();
    int code();
    String reason();
    static <T> MutableHttpResponse<T> ok();
    static <T> MutableHttpResponse<T> created(T body);
    static <T> MutableHttpResponse<T> badRequest();
    static <T> MutableHttpResponse<T> notFound();
}

public interface MutableHttpResponse<B> extends HttpResponse<B>, MutableHttpMessage<B> {
    MutableHttpResponse<B> status(HttpStatus status);
    MutableHttpResponse<B> header(CharSequence name, CharSequence value);
    MutableHttpResponse<B> body(B body);
}

// Filter interfaces
public interface HttpServerFilter extends ServerFilter {
    Publisher<MutableHttpResponse<?>> doFilter(HttpRequest<?> request,
                                             ServerFilterChain chain);
}

public interface ServerFilterChain {
    Publisher<MutableHttpResponse<?>> proceed(HttpRequest<?> request);
}

// Exception handling
public interface ExceptionHandler<T extends Throwable, R> {
    R handle(HttpRequest request, T exception);
}

// Route matching
public interface Router {
    <T, R> Stream<UriRouteMatch<T, R>> find(HttpMethod httpMethod, 
                                           CharSequence uri, 
                                           HttpRequest<?> context);
    <T, R> Optional<UriRouteMatch<T, R>> route(HttpMethod httpMethod, 
                                              CharSequence uri);
}

public interface RouteMatch<R> {
    R execute(Map<String, Object> argumentValues);
    Collection<Argument> getRequiredArguments(); 
    boolean isExecutable();
}

// File upload
public interface CompletedFileUpload extends FileUpload {
    byte[] getBytes();
    InputStream getInputStream();
    void moveTo(File destinationFile);
    Optional<MediaType> getContentType();
}

Install with Tessl CLI

npx tessl i tessl/maven-micronaut

docs

aop.md

configuration.md

dependency-injection.md

functions.md

http-client.md

http-server.md

index.md

management.md

messaging.md

reactive.md

retry.md

scheduling.md

websocket.md

tile.json