Modern, JVM-based framework for building modular, easily testable microservice and serverless applications with compile-time DI and fast startup.
—
Micronaut's HTTP client provides both programmatic and declarative APIs for making HTTP requests with reactive support, service discovery integration, and comprehensive configuration options.
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();
}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);
}
}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);
}
}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);
});
}
}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()));
}
}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);
});
}
}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;
}
}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);
});
}
}// 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