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-client.mddocs/

HTTP Client

Micronaut's HTTP client provides both programmatic and declarative APIs for making HTTP requests with reactive support, service discovery integration, and comprehensive configuration options.

Capabilities

Declarative Client

Create HTTP clients using interfaces with declarative annotations.

/**
 * Declarative HTTP client interface
 */
@Client("/api/users")
public interface UserClient {
    
    @Get("/{id}")
    Single<User> getUser(Long id);
    
    @Get
    Single<List<User>> getAllUsers(@QueryValue Optional<String> filter);
    
    @Post
    Single<User> createUser(@Body @Valid User user);
    
    @Put("/{id}")
    Single<User> updateUser(Long id, @Body @Valid User user);
    
    @Delete("/{id}")
    Completable deleteUser(Long id);
}

/**
 * Client with custom headers and error handling
 */
@Client(value = "${external.api.url}", 
        configuration = ApiClientConfiguration.class)
@Header(name = "User-Agent", value = "MyApp/1.0")
public interface ExternalApiClient {
    
    @Get("/data/{id}")
    @Header(name = "X-API-Key", value = "${api.key}")
    Single<ApiResponse> getData(String id);
    
    @Post("/webhooks")
    @Header(name = "Content-Type", value = "application/json")
    Completable sendWebhook(@Body WebhookPayload payload);
    
    @Get("/health")
    @Retryable(attempts = "3", delay = "1s")
    Single<HealthStatus> checkHealth();
}

Programmatic Client

Create and use HTTP clients programmatically for dynamic requests.

/**
 * Programmatic HTTP client usage
 */
@Singleton
public class ApiService {
    private final HttpClient httpClient;
    
    public ApiService(@Client("${api.base-url}") HttpClient httpClient) {
        this.httpClient = httpClient;
    }
    
    public Single<User> getUser(Long userId) {
        HttpRequest<Object> request = HttpRequest.GET("/users/" + userId)
            .header("Authorization", "Bearer " + getToken());
            
        return httpClient.retrieve(request, User.class);
    }
    
    public Single<User> createUser(User user) {
        HttpRequest<User> request = HttpRequest.POST("/users", user)
            .contentType(MediaType.APPLICATION_JSON);
            
        return httpClient.retrieve(request, User.class);
    }
    
    public Single<HttpResponse<User>> createUserWithResponse(User user) {
        HttpRequest<User> request = HttpRequest.POST("/users", user);
        
        return httpClient.exchange(request, User.class);
    }
}

Reactive Streaming

Stream large responses or handle real-time data with reactive types.

/**
 * Streaming HTTP client
 */
@Client("/api/stream")
public interface StreamingClient {
    
    @Get(value = "/events", processes = MediaType.TEXT_EVENT_STREAM)
    Flowable<ServerSentEvent<Event>> streamEvents();
    
    @Get("/large-data")
    Flowable<ByteBuffer<?>> streamLargeData();
    
    @Post("/upload")
    Single<UploadResult> uploadStream(@Body Flowable<ByteBuffer<?>> data);
}

/**
 * JSON streaming
 */
@Singleton
public class StreamingService {
    private final StreamingHttpClient streamingClient;
    
    public StreamingService(@Client("${api.url}") StreamingHttpClient streamingClient) {
        this.streamingClient = streamingClient;
    }
    
    public Flowable<User> streamUsers() {
        HttpRequest<Object> request = HttpRequest.GET("/users/stream");
        return streamingClient.jsonStream(request, User.class);
    }
    
    public Flowable<ByteBuffer<?>> downloadFile(String fileId) {
        HttpRequest<Object> request = HttpRequest.GET("/files/" + fileId);
        return streamingClient.dataStream(request);
    }
}

Client Configuration

Configure HTTP clients with timeouts, connection settings, and custom behavior.

/**
 * Client configuration class
 */
@ConfigurationProperties("http-client.api")
public class ApiClientConfiguration extends HttpClientConfiguration {
    
    @Override
    public Duration getReadTimeout() {
        return Duration.ofSeconds(30);
    }
    
    @Override
    public Duration getConnectTimeout() {
        return Duration.ofSeconds(10);
    }
    
    @Override
    public Integer getMaxContentLength() {
        return 1024 * 1024 * 10; // 10MB
    }
}

/**
 * Custom client configuration factory
 */
@Factory
public class HttpClientFactory {
    
    @Bean
    @Named("secure-client")
    public HttpClient createSecureClient() {
        return HttpClient.create("https://secure-api.example.com")
            .configuration(config -> {
                config.readTimeout(Duration.ofSeconds(60));
                config.followRedirects(true);
                config.maxContentLength(1024 * 1024 * 50); // 50MB
            });
    }
    
    @Bean
    @Named("fast-client") 
    public HttpClient createFastClient() {
        return HttpClient.create("https://fast-api.example.com")
            .configuration(config -> {
                config.readTimeout(Duration.ofSeconds(5));
                config.connectTimeout(Duration.ofSeconds(2));
                config.connectionPoolSize(20);
            });
    }
}

Client Filters

Add request/response filtering for authentication, logging, and other cross-cutting concerns.

/**
 * Client filter for authentication
 */
@Filter("/api/**")
public class AuthenticationClientFilter implements HttpClientFilter {
    
    @Override
    public Publisher<? extends HttpResponse<?>> doFilter(MutableHttpRequest<?> request,
                                                        ClientFilterChain chain) {
        String token = getAuthToken();
        request.header("Authorization", "Bearer " + token);
        return chain.proceed(request);
    }
    
    private String getAuthToken() {
        // Get authentication token
        return "token";
    }
}

/**
 * Logging client filter
 */
@Filter("/**")
public class LoggingClientFilter implements HttpClientFilter {
    
    @Override
    public Publisher<? extends HttpResponse<?>> doFilter(MutableHttpRequest<?> request,
                                                        ClientFilterChain chain) {
        log.info("Sending request: {} {}", request.getMethod(), request.getUri());
        
        return chain.proceed(request)
            .doOnNext(response -> 
                log.info("Received response: {} for {} {}", 
                    response.getStatus(), request.getMethod(), request.getUri()))
            .doOnError(error -> 
                log.error("Request failed: {} {} - {}", 
                    request.getMethod(), request.getUri(), error.getMessage()));
    }
}

Error Handling

Handle HTTP errors and network failures with custom error handling logic.

/**
 * Client with error handling
 */
@Client("/api")
public interface ResilientClient {
    
    @Get("/data")
    @Retryable(attempts = "3", delay = "2s", multiplier = "1.5")
    Single<Data> getData();
    
    @Get("/unreliable")
    @CircuitBreaker(attempts = "5", openStatusTimeout = "1m", resetTimeout = "30s")
    Single<String> getUnreliableData();
    
    @Fallback
    static Single<String> getUnreliableDataFallback() {
        return Single.just("fallback-data");
    }
}

/**
 * Custom error handling
 */
@Singleton
public class ApiClientService {
    private final HttpClient httpClient;
    
    public ApiClientService(@Client("${api.url}") HttpClient httpClient) {
        this.httpClient = httpClient;
    }
    
    public Single<User> getUserWithErrorHandling(Long id) {
        HttpRequest<Object> request = HttpRequest.GET("/users/" + id);
        
        return httpClient.exchange(request, User.class)
            .flatMap(response -> {
                if (response.getStatus().getCode() == 404) {
                    return Single.error(new UserNotFoundException(id));
                } else if (response.getStatus().getCode() >= 400) {
                    return Single.error(new ApiException(response.getStatus()));
                }
                return Single.just(response.body());
            })
            .onErrorResumeNext(throwable -> {
                if (throwable instanceof ConnectException) {
                    return Single.error(new ServiceUnavailableException());
                }
                return Single.error(throwable);
            });
    }
}

Service Discovery Integration

Integrate with service discovery systems for dynamic service resolution.

/**
 * Client with service discovery
 */
@Client(id = "user-service", path = "/api/users")
public interface UserServiceClient {
    
    @Get("/{id}")
    Single<User> getUser(Long id);
    
    @Get
    Single<List<User>> getAllUsers();
}

/**
 * Load balancing configuration
 */
@ConfigurationProperties("http-client.user-service")
public class UserServiceClientConfiguration extends HttpClientConfiguration {
    
    @Override
    public Duration getReadTimeout() {
        return Duration.ofSeconds(15);
    }
    
    // Load balancer configuration
    public LoadBalancer getLoadBalancer() {
        return LoadBalancer.ROUND_ROBIN;
    }
}

File Upload and Download

Handle file operations with progress tracking and streaming support.

/**
 * File operations client
 */
@Client("/api/files")
public interface FileClient {
    
    @Post(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA)
    Single<FileUploadResult> uploadFile(@Part CompletedFileUpload file,
                                       @Part("metadata") FileMetadata metadata);
    
    @Get("/{fileId}")
    Single<byte[]> downloadFile(String fileId);
    
    @Get(value = "/{fileId}/stream")
    Flowable<ByteBuffer<?>> streamFile(String fileId);
    
    @Delete("/{fileId}")
    Completable deleteFile(String fileId);
}

/**
 * Large file handling
 */
@Singleton
public class FileService {
    private final StreamingHttpClient streamingClient;
    
    public FileService(@Client("${file.service.url}") StreamingHttpClient streamingClient) {
        this.streamingClient = streamingClient;
    }
    
    public Single<String> uploadLargeFile(File file) {
        HttpRequest<Object> request = HttpRequest.POST("/upload", file)
            .contentType(MediaType.APPLICATION_OCTET_STREAM)
            .header("Content-Length", String.valueOf(file.length()));
            
        return streamingClient.retrieve(request, String.class);
    }
    
    public void downloadLargeFile(String fileId, File destination) {
        HttpRequest<Object> request = HttpRequest.GET("/files/" + fileId);
        
        streamingClient.dataStream(request)
            .subscribe(buffer -> {
                // Write buffer to file
                writeToFile(buffer, destination);
            });
    }
}

Types

// Core client interfaces
public interface HttpClient extends Closeable, LifeCycle<HttpClient> {
    <I, O> Publisher<HttpResponse<O>> exchange(HttpRequest<I> request, 
                                              Argument<O> bodyType);
    <I, O> Publisher<HttpResponse<O>> exchange(HttpRequest<I> request, 
                                              Class<O> bodyType);
    <I, O> Publisher<O> retrieve(HttpRequest<I> request, Argument<O> bodyType);
    <I, O> Publisher<O> retrieve(HttpRequest<I> request, Class<O> bodyType);
    
    BlockingHttpClient toBlocking();
    static HttpClient create(URL url);
}

public interface StreamingHttpClient extends HttpClient {
    <I> Publisher<ByteBuffer<?>> dataStream(HttpRequest<I> request);
    <I> Publisher<Map<String, Object>> jsonStream(HttpRequest<I> request);
    <I, O> Publisher<O> jsonStream(HttpRequest<I> request, Argument<O> type);
}

public interface BlockingHttpClient extends Closeable, LifeCycle<BlockingHttpClient> {
    <I, O> HttpResponse<O> exchange(HttpRequest<I> request, Class<O> bodyType);
    <I, O> O retrieve(HttpRequest<I> request, Class<O> bodyType);
    String retrieve(String uri);
}

// Client configuration
public class HttpClientConfiguration {
    public Duration getReadTimeout();
    public Duration getConnectTimeout();
    public Duration getConnectionPoolIdleTimeout();
    public Integer getMaxContentLength();
    public boolean isFollowRedirects();
    public ProxyConfiguration getProxyConfiguration();
    public HttpVersion getHttpVersion();
}

// Client filtering
public interface HttpClientFilter extends ClientFilter {
    Publisher<? extends HttpResponse<?>> doFilter(MutableHttpRequest<?> request,
                                                 ClientFilterChain chain);
}

public interface ClientFilterChain {
    Publisher<? extends HttpResponse<?>> proceed(MutableHttpRequest<?> request);
}

// Request creation
public final class HttpRequest {
    public static <T> MutableHttpRequest<T> GET(String uri);
    public static <T> MutableHttpRequest<T> POST(String uri, T body);
    public static <T> MutableHttpRequest<T> PUT(String uri, T body);
    public static <T> MutableHttpRequest<T> DELETE(String uri);
    public static <T> MutableHttpRequest<T> HEAD(String uri);
    public static <T> MutableHttpRequest<T> OPTIONS(String uri);
    public static <T> MutableHttpRequest<T> PATCH(String uri, T body);
    public static <T> MutableHttpRequest<T> TRACE(String uri);
}

public interface MutableHttpRequest<B> extends HttpRequest<B>, MutableHttpMessage<B> {
    MutableHttpRequest<B> cookie(Cookie cookie);
    MutableHttpRequest<B> uri(URI uri);
    MutableHttpRequest<B> header(CharSequence name, CharSequence value);
    MutableHttpRequest<B> headers(Map<CharSequence, CharSequence> headers);
    MutableHttpRequest<B> contentType(MediaType mediaType);
    MutableHttpRequest<B> accept(MediaType... mediaTypes);
    MutableHttpRequest<B> basicAuth(String username, String password);
    MutableHttpRequest<B> bearerAuth(String token);
}

// Service discovery types
public enum LoadBalancer {
    ROUND_ROBIN,
    RANDOM,
    STICKY
}

public interface ServiceDiscoveryClient {
    Publisher<List<ServiceInstance>> getInstances(String serviceId);
    String getDescription();
}

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