Introduction
When building modern microservices applications, consuming external REST services is a common requirement. Quarkus provides powerful built-in support for REST clients, offering both traditional Java EE approaches and modern MicroProfile solutions. In this article, we'll focus on the MicroProfile REST Client approach, as it provides a more modern, maintainable, and feature-rich way to build REST clients in Quarkus applications. This approach aligns better with microservices architectures and provides superior integration with other MicroProfile features like Config, Fault Tolerance, and Metrics.
It offers:
- Declarative, interface-based client definitions;
- Type-safe request/response handling;
- Automatic client generation;
- Built-in integration with MicroProfile Configl
- Better integration with CDI and other MicroProfile features;
- Simplified error handling and resilience pattern.
Getting Started
-
Adding Required Dependencies
<dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-rest-client-jackson</artifactId> </dependency>
-
Creating Your First REST Client
@RegisterRestClient(configKey = "simple-api") public interface SimpleRestClient { @GET @Path("/simple") Response simple(); }
Configuration
Configure your REST client in application.properties:
quarkus.rest-client.simple-api.url=http://simple-api:8080
-
@RegisterRestClient
: Registers the interface for CDI injection as a REST Client; -
@Path
,@GET
: Standard Jakarta REST annotations defining service access; -
configKey
: Allows using a custom configuration key instead of the fully qualified interface name.
Real-World Example: Cryptocurrency API Client
Let's build a practical example using the CoinCap API to fetch cryptocurrency data.
@RegisterRestClient(configKey = "crypto-currency")
public interface CryptoCurrencyRestClient {
@GET
@Path("/assets")
AssetData getAssets();
@GET
@Path("/assets/{id}/history")
AssetHistoryData getAssetHistory(
@PathParam("id") String id,
@QueryParam("interval") String interval
);
}
and the record classes:
public record AssetData(List<Crypto> data) {
public record Crypto(String id,
String rank,
String symbol,
String name,
String supply,
String maxSupply,
String marketCapUsd,
String volumeUsd24Hr,
String priceUsd,
String changePercent24Hr,
String vwap24Hr,
String explorer) {
}
}
public record AssetHistoryData(List<DataPoint> data) {
public record DataPoint(
String priceUsd,
long time,
String circulatingSupply,
String date
) {}
}
You can easily inject and use the REST client within your Quarkus application. Implementation in a Resource:
@Path("/crypto")
public class CryptoCurrencyResource {
private final CryptoCurrencyRestClient cryptoCurrencyRestClient;
public CryptoCurrencyResource(@RestClient CryptoCurrencyRestClient cryptoCurrencyRestClient) {
this.cryptoCurrencyRestClient = cryptoCurrencyRestClient;
}
@GET
public AssetData getCurrency() {
return cryptoCurrencyRestClient.getAssets();
}
}
Advanced Features
Fault Tolerance
In distributed systems, network calls can fail. Quarkus provides robust fault tolerance features through SmallRye Fault Tolerance:
-
Add the dependency:
<dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-smallrye-fault-tolerance</artifactId> </dependency>
Implement fault tolerance patterns:
Retry Pattern
The @Retry
annotation provides sophisticated retry capabilities when service calls fail. Here are the key configuration options:
@GET
@Path("/assets/{id}/history")
@Retry(
maxRetries = 5, // Maximum number of retry attempts
delay = 200, // Delay between retries in milliseconds
jitter = 100, // Random variation added to delay
retryOn = { // Specify which exceptions trigger retry
IOException.class,
TimeoutException.class
},
abortOn = { // Exceptions that immediately abort retry
SecurityException.class,
IllegalArgumentException.class
},
maxDuration = 2000 // Maximum duration for all retry attempts
)
AssetHistoryData getAssetHistory(
@PathParam("id") String id,
@QueryParam("interval") String interval
);
You can also implement exponential backoff:
@Retry(
maxRetries = 3,
delay = 1000,
delayUnit = ChronoUnit.MILLIS,
exponential = true // Enable exponential backoff
)
Fallback Pattern
Fallbacks provide alternative behavior when all retry attempts fail. You can implement fallbacks in multiple ways:
@GET
@Path("/assets")
@Retry(maxRetries = 2)
@Fallback(fallbackMethod = "getDefaultAssets")
AssetData getAssets();
// Fallback method must have the same return type and parameters
private AssetData getDefaultAssets() {
log.warn("Using fallback method for getAssets");
return AssetData.builder()
.timestamp(System.currentTimeMillis())
.status("FALLBACK")
.build();
}
Circuit Breaker Pattern
The Circuit Breaker pattern prevents cascade failures in distributed systems. Here's a detailed configuration:
@GET
@Path("/assets")
@CircuitBreaker(
requestVolumeThreshold = 4, // Minimum requests before CB can trip
failureRatio = 0.5, // Failure ratio to trip CB (50%)
delay = 1000, // Time CB stays open before half-open
successThreshold = 2, // Successes needed to close CB
failOn = { // Exceptions counted as failures
IOException.class,
TimeoutException.class
},
skipOn = { // Exceptions not counted as failures
BusinessException.class
}
)
@Fallback(fallbackMethod = "getDefaultAssets") // Combine with fallback
AssetData getAssets();
Circuit Breaker States:
- CLOSED: Normal operation, calls pass through
- OPEN: Calls fail fast without executing
- HALF-OPEN: Limited calls allowed to test service recovery
Dynamic Base URL Configuration
The @Url
annotation provides flexible runtime URL configuration:
-
Basic Usage:
@GET @Path("/assets") AssetData getAssets(@Url String url); // Usage in code client.getAssets("https://alternate-api.example.com/v2");
-
Combining with Path Parameters:
@GET @Path("/assets/{id}") AssetData getAssetById(@Url String baseUrl, @PathParam("id") String id); // Usage client.getAssetById("https://api-backup.example.com", "bitcoin");
-
URL Priority:
// Configuration priority (highest to lowest): // 1. @Url parameter // 2. Programmatic configuration // 3. application.properties // 4. Default URL (if specified in @RegisterRestClient) @RegisterRestClient(baseUri = "https://default.api.example.com") public interface DynamicRestClient { @GET @Path("/resource") Response getResource(@Url String url); }
Custom Headers Management
Header management is crucial for authentication, tracking, and protocol compliance:
-
Static Headers
@RegisterRestClient(configKey = "api-client") @ClientHeaderParam(name = "API-Version", value = "1.0") @ClientHeaderParam(name = "Client-ID", value = "${app.client.id}") public interface ApiClient { @GET @Path("/data") Response getData(); }
Dynamic Headers using Methods:
@RegisterRestClient(configKey = "secure-api")
public interface SecureApiClient {
@GET
@Path("/protected-resource")
@ClientHeaderParam(name = "Authorization", value = "{generateAuthHeader}")
@ClientHeaderParam(name = "Request-Time", value = "{generateTimestamp}")
Response getProtectedResource();
default String generateAuthHeader() {
return "Bearer " + TokenGenerator.generateToken();
}
default String generateTimestamp() {
return String.valueOf(System.currentTimeMillis());
}
}
Real-World Example: Integrating with Freesound API
Let's explore a practical example of building a REST client for the Freesound API, which demonstrates header-based authentication and parameter handling.
-
Creating the REST Client Interface
@RegisterRestClient(configKey = "free-sound") @ClientHeaderParam(name = "Authorization", value ={getAuthorizationHeader}") public interface FreeSoundRestClient { default String getAuthorizationHeader() { Config config = ConfigProvider.getConfig(); String apiKey = config.getConfigValue("free.sound.api.key").getValue(); return "Token " + apiKey; } @GET @Path("/users/{username}") UserResponse getUsers(@PathParam("username") String username); }
-
Configuration Setup
# Base URL configuration quarkus.rest-client.free-sound.url=https://freesound.org/apiv2 # API Key configuration free.sound.api.key=${FREESOUND_API_KEY}
-
Response Data Models
public record UserResponse(String url, String username, String about, String home_page, Avatar avatar, LocalDateTime date_joined, int num_sounds, String sounds, int num_packs, String packs, int num_posts, int num_comments) { public record Avatar( String small, String medium, String large ) {} }
-
Using the Client in a Resource
@Path("/freesound") @Produces(MediaType.APPLICATION_JSON) public class FreeSoundResource { private final FreeSoundRestClient freeSoundClient; public FreeSoundResource(@RestClient FreeSoundRestClient freeSoundClient) { this.freeSoundClient = freeSoundClient; } @GET @Path("/users/{username}") public UserResponse getUserProfile(@PathParam("username") String username) { return freeSoundClient.getUsers(username); } }
Conclusion
Quarkus REST Client provides a powerful and flexible way to consume REST services in your microservices architecture. By combining MicroProfile REST Client with Quarkus's fault tolerance features, you can build robust and reliable service communications.
The full source code for this article is available on GitHub
Top comments (0)