How to code a Quarkus REST Client
Learn how to implement a type-safe REST Client in Quarkus 3.x using MicroProfile REST Client and the high-performance RESTEasy Reactive stack (Quarkus REST). This guide demonstrates endpoint creation, client interface definitions, Jakarta EE 10 (jakarta.*) migration, dynamic connection configuration, and testing.
This article is a walk through Quarkus REST Client API using MicroProfile REST Client. We will develop a basic REST Endpoint and then we will set up a simple Client project with a Service interface for our REST Service updated for Quarkus 3.x.
When developing REST Client API, Quarkus offers two options:
- JAX-RS Web Client: This is the standard Jakarta EE way of communicating with RESTful services (Learn more in this article: JAX-RS client API tutorial )
- MicroProfile REST Client: This is the modern, type-safe approach for communicating with RESTful services when using MicroProfile and Quarkus applications.
In this article we will learn how to develop a Quarkus REST Client using MicroProfile REST Client on top of the modern Quarkus 3 reactive architecture.
Getting started with MicroProfile REST Client
The MicroProfile REST Client provides a type-safe approach to map REST Endpoints in a similar way to Jakarta REST (formerly JAX-RS) specifications. The MicroProfile REST client is defined as a Java interface, which makes it compile-time type-safe. In addition, Quarkus allows you to seamlessly switch between synchronous calls and reactive Mutiny types (such as Uni and Multi), while decoupling the Client/Server connection entirely into configuration.
Let’s start. We will build at first a basic Server Endpoint and then we will create the Client project to access it.
Building the Server project
Firstly, bootstrap a Quarkus 3 project using modern RESTEasy Reactive extensions (rest and rest-jackson) to allow producing/consuming JSON REST resources:
mvn io.quarkus.platform:quarkus-maven-plugin:3.15.0:create \
-DprojectGroupId=com.mastertheboss \
-DprojectArtifactId=rest-server \
-Dpath="/api" \
-Dextensions="rest,rest-jackson"
Next, let’s code a minimal Endpoint using the jakarta.ws.rs namespace which can return a simple text and a JSON Media type with some random data:
package com.mastertheboss;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import java.util.UUID;
@Path("/api")
public class SimpleRESTService {
@GET
@Path("/text")
public String getHello()
{
return "hello world!";
}
@GET
@Path("/json")
@Produces(MediaType.APPLICATION_JSON)
public SimpleProperty getPropertyJSON()
{
SimpleProperty p = new SimpleProperty(UUID.randomUUID().toString(), UUID.randomUUID().toString());
return p;
}
}
Finish by adding the SimpleProperty POJO:
package com.mastertheboss;
public class SimpleProperty {
private String key;
private String value;
public SimpleProperty() {}
public SimpleProperty(String key, String value) {
this.key = key;
this.value = value;
}
public String getKey() { return key; }
public void setKey(String key) { this.key = key; }
public String getValue() { return value; }
public void setValue(String value) { this.value = value; }
}
The Server project is ready. To test it, just include the following QuarkusTest Class in it:
package com.mastertheboss;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.is;
@QuarkusTest
public class SimpleRESTServiceTest {
@Test
public void testHelloEndpoint() {
given()
.when().get("/api/text")
.then()
.statusCode(200)
.body(is("hello world!"));
}
}
Then, start the server project on the default Quarkus Port (8080):
mvn install quarkus:dev
Building the REST Client project
To bootstrap our Client project in Quarkus 3.x, we will use the reactive client extensions rest-client and rest-client-jackson alongside the standard server extensions:
mvn io.quarkus.platform:quarkus-maven-plugin:3.15.0:create \
-DprojectGroupId=com.mastertheboss \
-DprojectArtifactId=rest-client \
-Dpath="/proxy" \
-Dextensions="rest,rest-jackson,rest-client,rest-client-jackson"
Within our Client project, we will add two resources:
- A REST Endpoint (SimpleRESTEndpoint) which is available externally and contains the definition of the @RestClient injection.
- A Service Interface (SimpleRESTServiceItf) which is registered as REST Client with the annotation @RegisterRestClient.
Let’s start from the Service Interface using Jakarta EE annotations:
package com.mastertheboss;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@RegisterRestClient
@Path("/api")
public interface SimpleRESTServiceItf {
@GET
@Path("/text")
public String getHello();
@GET
@Path("/json")
@Produces(MediaType.APPLICATION_JSON)
public SimpleProperty getPropertyJSON();
}
Next, our REST Client Endpoint, which declares the Service Interface using org.eclipse.microprofile.rest.client.inject.RestClient and CDI's jakarta.enterprise.context.ApplicationScoped:
package com.mastertheboss;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.rest.client.inject.RestClient;
@Path("/proxy")
@ApplicationScoped
public class SimpleRESTEndpoint {
@Inject
@RestClient
SimpleRESTServiceItf service;
@GET
@Path("/text")
public String getHello() {
return service.getHello();
}
@GET
@Path("/json")
@Produces(MediaType.APPLICATION_JSON)
public SimpleProperty getPropertyJSON(){
return service.getPropertyJSON();
}
}
Include as well the SimpleProperty POJO in the Client project. To test our REST Client, we will include the following simple Test Class:
package com.mastertheboss;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.is;
@QuarkusTest
public class SimpleRESTServiceItfTest {
@Test
public void testHelloEndpoint() {
given()
.when().get("/proxy/text")
.then()
.statusCode(200)
.body(is("hello world!"));
}
}
Configuring the Client/Server connection
Our Client project still needs to know about the Remote REST Endpoint. There are two ways to configure Client/Server connectivity:
- Using MicroProfile standard notation
- Using Quarkus notation
Firstly, we will use the standard MicroProfile notation for REST Clients. Open the application.properties file and add the following configuration updated for Jakarta EE scopes:
com.mastertheboss.SimpleRESTServiceItf/mp-rest/url=http://localhost:8080
com.mastertheboss.SimpleRESTServiceItf/mp-rest/scope=jakarta.inject.Singleton
As you can see from the first line, all requests to the REST Client Interface will result in a call to the Remote Endpoint Base URL, which is qualified using this expression:
<Fully Qualified REST Client Interface>/mp-rest/url=<Remote REST base URL>
Also, the default scope of the REST Client Interface is set to Singleton which instructs Quarkus to instantiate the singleton once, passing its reference to other objects during injection. Other supported scope values are @Dependent, jakarta.enterprise.context.ApplicationScoped, and jakarta.enterprise.context.RequestScoped.
Testing the REST Client
Your Client Project is ready for testing! Start it in development mode, switching to another HTTP Port to avoid clashing with the server:
mvn clean install quarkus:dev -Dquarkus.http.port=9080
Your Test Class will run and you should see a successful execution similar to this:
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.mastertheboss.SimpleRESTServiceItfTest
2024-10-15 11:41:07,013 INFO [io.quarkus] (main) Quarkus 3.15.0 on JVM started in 1.395s. Listening on: http://localhost:8081
2024-10-15 11:41:07,015 INFO [io.quarkus] (main) Profile test activated.
2024-10-15 11:41:07,015 INFO [io.quarkus] (main) Installed features: [cdi, rest, rest-client, rest-client-jackson, rest-jackson, smallrye-context-propagation, vertx]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 3.211 s - in com.mastertheboss.SimpleRESTServiceItfTest
2024-10-15 11:41:09,036 INFO [io.quarkus] (main) Quarkus stopped in 0.035s
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
Additionally, you can test the proxy endpoint directly using cURL:
curl http://localhost:9080/proxy/json
{"key":"dbfa5152-f6cc-42c0-b3d6-bf4e21b3ffec","value":"c2dffab7-553c-4e23-a318-4964aa51faf6"}
Using Quarkus notation to configure Client/Server connectivity
The other option you can use to map the REST Client with the remote Endpoint is via Quarkus notation. The advantage of this approach is that you can completely decouple the FQ Class name of your Interface from your configuration file.
Proceed as follows: add the configKey attribute to your @RegisterRestClient annotation:
@RegisterRestClient(configKey="hello-service")
@Path("/api")
public interface SimpleRESTServiceItf {
//
}
Now the Interface is known to Quarkus as “hello-service”. You can then update application.properties to use the configKey mapping:
quarkus.rest-client.hello-service.url=http://localhost:8080
Frequently Asked Questions (FAQs)
How do I write a reactive REST client in Quarkus 3 using Mutiny?
With Quarkus REST (RESTEasy Reactive client), you can simply change the interface return type to Mutiny types like io.smallrye.mutiny.Uni<T> or io.smallrye.mutiny.Multi<T>. Quarkus will execute client invocations in a non-blocking manner automatically without requiring additional thread management.
What changed between Quarkus 2.x and Quarkus 3.x regarding REST Clients?
Quarkus 3.x migrated from Java EE (javax.*) packages to Jakarta EE 10 (jakarta.*). Furthermore, Quarkus 3 defaults to the reactive RESTEasy Reactive stack (quarkus-rest and quarkus-rest-client), providing significantly lower resource usage and faster execution times compared to classic blocking RESTEasy dependencies.
Which configuration approach should I prefer: MicroProfile or Quarkus configKey?
While standard MicroProfile property configuration (FQCN/mp-rest/url) works well for portable applications, using the Quarkus configKey attribute (e.g., quarkus.rest-client.my-service.url) is strongly recommended. It keeps properties clean, refactoring-safe, and easier to override across execution environments.
Conclusion
In this article we have gone through MicroProfile REST API in Quarkus 3.x to learn how to create a type-safe Interface for remote REST Endpoints. If you want to learn how to configure and run a similar example on WildFly, check this article: A simple example of MicroProfile REST Client API
The source code for this article is available here: https://github.com/fmarchioni/mastertheboss/tree/master/quarkus/microprofile-rest-client
Recommended Articles
Query Quarkus REST Service with Ajax and jQuery
Learn how to create an Ajax front-end to a Quarkus REST application using jQuery and query a sample REST service running on Quarkus 0.16.1
Create a Quarkus REST CRUD Application with Hibernate Panache and REST Data Panache - Tutorial
Learn how to create a robust REST CRUD application in Quarkus using Hibernate Panache and REST Data Panache. #Quarkus #HibernatePanache #RESTDataPanache
Build a REST Application with MongoDB and Quarkus: A Step-by-Step Guide
Learn how to create a REST application with MongoDB NoSQL Database and Quarkus. Get started with MongoDB, explore the MongoDB Java Client and Hibernate Panache approaches, and build a sample application.
Mastering Servlet Context in Quarkus REST Applications
Learn how to effectively use Servlet Context in your Quarkus-based REST applications with this expert guide.