Logo

dev-resources.site

for different kinds of informations.

Building Robust REST Client with Quarkus: A Comprehensive Guide

Published at
12/27/2024
Categories
quarkus
microprofile
rest
java
Author
yanev
Categories
4 categories in total
quarkus
open
microprofile
open
rest
open
java
open
Author
5 person written this
yanev
open
Building Robust REST Client with Quarkus: A Comprehensive Guide

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.

Rest Client

Getting Started

  1. Adding Required Dependencies

    <dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-rest-client-jackson</artifactId>
    </dependency>
    
  2. Creating Your First REST Client

        @RegisterRestClient(configKey = "simple-api")
         public interface SimpleRestClient {
         @GET
         @Path("/simple")
         Response simple();
      }
    
  3. Configuration

Configure your REST client in application.properties:

quarkus.rest-client.simple-api.url=http://simple-api:8080
Enter fullscreen mode Exit fullscreen mode
  • @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
    );
}
Enter fullscreen mode Exit fullscreen mode

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
  ) {}
}
Enter fullscreen mode Exit fullscreen mode

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();
    }
}
Enter fullscreen mode Exit fullscreen mode

Advanced Features

Fault Tolerance

In distributed systems, network calls can fail. Quarkus provides robust fault tolerance features through SmallRye Fault Tolerance:

  1. Add the dependency:

    <dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-smallrye-fault-tolerance</artifactId>
    </dependency>
    
  2. 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
);
Enter fullscreen mode Exit fullscreen mode

You can also implement exponential backoff:

@Retry(
    maxRetries = 3,
    delay = 1000,
    delayUnit = ChronoUnit.MILLIS,
    exponential = true                 // Enable exponential backoff
)
Enter fullscreen mode Exit fullscreen mode

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();
}
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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:

  1. Basic Usage:

     @GET
     @Path("/assets")
     AssetData getAssets(@Url String url);
     // Usage in code
     client.getAssets("https://alternate-api.example.com/v2");
    
  2. 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");
    
  3. 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:

  1. 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();
    }
    
  2. 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());
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

  1. 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);    
    }
    
  2. 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}
    
  3. 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
      ) {}
    }
    
  4. 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

rest Article's
30 articles in total
Favicon
Best Practices for Securing REST APIs: Balancing Performance, Usability, and Security
Favicon
Learning REST APIs in JavaScript
Favicon
Validation in Spring REST Framework (SRF)
Favicon
API Design Best Practices in 2025: REST, GraphQL, and gRPC
Favicon
GraphQL vs REST: When to Choose Which for Your Node.js Backend
Favicon
REST VS SOAP
Favicon
Discover the 9 Best Open-Source Alternatives to Postman
Favicon
Building Robust REST Client with Quarkus: A Comprehensive Guide
Favicon
O que Γ© REST API?
Favicon
Building Async APIs in ASP.NET Core - The Right Way
Favicon
Why Clear and Meaningful Status Codes Matter in Your REST API
Favicon
Understanding Request and Response Headers in REST APIs
Favicon
How Scale Changes Everything - The LiveAPI Perspective
Favicon
A Closer Look At API Docs Generated via LiveAPI's AI
Favicon
Quick and Easy: How to Test RESTful APIs in Java
Favicon
Understanding API Architectural Styles: REST, GraphQL, SOAP and More
Favicon
Implementing Pagination, Filtering, and Sorting in REST APIs
Favicon
REST In Peace
Favicon
Understanding HTTP Status Codes
Favicon
Musings Over What Makes LiveAPI Different (from Swagger Et Cetera)
Favicon
Introduction to APIs: Supercharging Your Web Development Journey
Favicon
An Innovative Way to Create REST APIs
Favicon
Best Practices for Developing and Integrating REST APIs into Web Applications
Favicon
Как ΠΏΠΎΠ΄Ρ€ΡƒΠΆΠΈΡ‚ΡŒ ΠΊΠΎΡ‚ΠΈΠΊΠΎΠ², слонов ΠΈ ΠΊΠΈΡ‚ΠΎΠ²: тСстированиС Spring-ΠΏΡ€ΠΈΠ»ΠΎΠΆΠ΅Π½ΠΈΠΉ с Testcontainers πŸ±πŸ˜πŸ‹
Favicon
Implementing Idempotent REST APIs in ASP.NET Core
Favicon
Understanding REST vs. GraphQL: Which One Should You Choose?
Favicon
Problem Details for ASP.NET Core APIs
Favicon
REST vs. GraphQL: Key Differences, Benefits, and Which One to Choose for Your Project
Favicon
REST vs. GraphQL: Choosing the Right API for Your Project
Favicon
Optimizing Your REST Assured Tests: Setting Default Host and Port, GET Requests, and Assertions

Featured ones: