REST Client
A typical scenario in a Microservices architecture is the remote invocation of remote REST HTTP endpoints. Quarkus provides a typed REST client that follows the MicroProfile REST Client specification.
Let’s create a REST client that accesses https://swapi.info to get additional information about Movies. The endpoint we’re interested in is this one:
-
api/films/{id}, which returns specific info about the given movie.
{
"title": "A New Hope",
"episode_id": 4,
"opening_crawl": "It is a period of civil war.\r\nRebel spaceships, striking\r\nfrom a hidden base, have won\r\ntheir first victory against\r\nthe evil Galactic Empire.\r\n\r\nDuring the battle, Rebel\r\nspies managed to steal secret\r\nplans to the Empire's\r\nultimate weapon, the DEATH\r\nSTAR, an armored space\r\nstation with enough power\r\nto destroy an entire planet.\r\n\r\nPursued by the Empire's\r\nsinister agents, Princess\r\nLeia races home aboard her\r\nstarship, custodian of the\r\nstolen plans that can save her\r\npeople and restore\r\nfreedom to the galaxy....",
"director": "George Lucas",
"producer": "Gary Kurtz, Rick McCallum",
"release_date": "1977-05-25",
"characters": [
"https://swapi.info/api/people/1",
"https://swapi.info/api/people/2",
"https://swapi.info/api/people/3",
"https://swapi.info/api/people/4",
"https://swapi.info/api/people/5",
"https://swapi.info/api/people/6",
"https://swapi.info/api/people/7",
"https://swapi.info/api/people/8",
"https://swapi.info/api/people/9",
"https://swapi.info/api/people/10",
"https://swapi.info/api/people/12",
"https://swapi.info/api/people/13",
"https://swapi.info/api/people/14",
"https://swapi.info/api/people/15",
"https://swapi.info/api/people/16",
"https://swapi.info/api/people/18",
"https://swapi.info/api/people/19",
"https://swapi.info/api/people/81"
],
"planets": [
"https://swapi.info/api/planets/1",
"https://swapi.info/api/planets/2",
"https://swapi.info/api/planets/3"
],
"starships": [
"https://swapi.info/api/starships/2",
"https://swapi.info/api/starships/3",
"https://swapi.info/api/starships/5",
"https://swapi.info/api/starships/9",
"https://swapi.info/api/starships/10",
"https://swapi.info/api/starships/11",
"https://swapi.info/api/starships/12",
"https://swapi.info/api/starships/13"
],
"vehicles": [
"https://swapi.info/api/vehicles/4",
"https://swapi.info/api/vehicles/6",
"https://swapi.info/api/vehicles/7",
"https://swapi.info/api/vehicles/8"
],
"species": [
"https://swapi.info/api/species/1",
"https://swapi.info/api/species/2",
"https://swapi.info/api/species/3",
"https://swapi.info/api/species/4",
"https://swapi.info/api/species/5"
],
"created": "2014-12-10T14:23:31.880000Z",
"edited": "2014-12-20T19:49:45.256000Z",
"url": "https://swapi.info/api/films/1"
}
Add the REST Client extension
Open a new terminal window, and make sure you’re at the root of your tutorial-app project, then run:
./mvnw quarkus:add-extension -D"extensions=rest-client, rest-client-jackson"
quarkus extension add rest-client rest-client-jackson
[INFO] Scanning for projects...
[INFO]
[INFO] -----------------< com.redhat.developers:tutorial-app >-----------------
[INFO] Building tutorial-app 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- quarkus-maven-plugin:3.10.2:add-extension (default-cli) @ tutorial-app ---
✅ Adding extension io.quarkus:quarkus-rest-client-jackson
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.773 s
[INFO] Finished at: 2024-05-23T21:43:38-04:00
[INFO] ------------------------------------------------------------------------
Notice in the logs how Quarkus is reloading and the rest-client-jackson extension is now part of the Installed features.
Create Swapi POJO
We need to create a POJO object that is used to unmarshal a JSON message from http://swapi.dev.
Create a new Swapi Java class in src/main/java in the com.redhat.developers package with the following contents:
package com.redhat.developers;
public class Swapi {
private String title;
private int episode_id;
private String opening_crawl;
private String director;
private String producer;
public Swapi() {
}
public Swapi(String title, int episode_id, String opening_crawl, String director, String producer) {
this.title = title;
this.episode_id = episode_id;
this.opening_crawl = opening_crawl;
this.director = director;
this.producer = producer;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getEpisode_id() {
return episode_id;
}
public void setEpisode_id(int episode_id) {
this.episode_id = episode_id;
}
public String getOpening_crawl() {
return opening_crawl;
}
public void setOpening_crawl(String opening_crawl) {
this.opening_crawl = opening_crawl;
}
public String getDirector() {
return director;
}
public void setDirector(String director) {
this.director = director;
}
public String getProducer() {
return producer;
}
public void setProducer(String producer) {
this.producer = producer;
}
}
Create SwapiService
Now we’re going to implement a Java interface that mimics the remote REST endpoint.
Create a new SwapiService Java interface in src/main/java in the com.redhat.developers package with the following contents:
package com.redhat.developers;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
@RegisterRestClient
public interface SwapiService {
@GET
@Produces(MediaType.APPLICATION_JSON)
@Path("/films/{id}")
public Swapi getFilmById(@PathParam("id") String id);
}
Configure REST Client properties
Add the following properties to your application.properties in src/main/resources:
quarkus.rest-client."com.redhat.developers.SwapiService".url=https://swapi.info/api
Create MovieDTO
We’re going to enhance our MovieResource endpoint by creating a new MovieDTO POJO and add the additional information provided by the SwapiService.
Create a new MovieDTO Java class in src/main/java in the com.redhat.developers package with the following contents:
package com.redhat.developers;
import java.sql.Date;
public class MovieDTO {
private String title;
private Date releaseDate;
private int episode_id;
private String opening_crawl;
private String director;
private String producer;
public MovieDTO() {
}
private MovieDTO(String title, Date releaseDate, int episode_id, String opening_crawl, String director, String producer) {
this.title = title;
this.releaseDate = releaseDate;
this.episode_id = episode_id;
this.opening_crawl = opening_crawl;
this.director = director;
this.producer = producer;
}
public static MovieDTO of(Movie movie, Swapi swapi) {
return new MovieDTO(
movie.title,
movie.releaseDate,
swapi.getEpisode_id(),
swapi.getOpening_crawl(),
swapi.getDirector(),
swapi.getProducer()
);
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Date getReleaseDate() {
return releaseDate;
}
public void setReleaseDate(Date releaseDate) {
this.releaseDate = releaseDate;
}
public int getEpisode_id() {
return episode_id;
}
public void setEpisode_id(int episode_id) {
this.episode_id = episode_id;
}
public String getOpening_crawl() {
return opening_crawl;
}
public void setOpening_crawl(String opening_crawl) {
this.opening_crawl = opening_crawl;
}
public String getDirector() {
return director;
}
public void setDirector(String director) {
this.director = director;
}
public String getProducer() {
return producer;
}
public void setProducer(String producer) {
this.producer = producer;
}
}
Change MovieResource to use SwapiService
Now that we have all the required classes, we can change MovieResource to get movies by title and use our SwapiService REST client via the @RestClient annotation.
Change the MovieResource Java class in src/main/java in the com.redhat.developers package with the following contents:
package com.redhat.developers;
import jakarta.transaction.Transactional;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import java.util.List;
import java.util.stream.Collectors;
@Path("movie")
@Produces(MediaType.APPLICATION_JSON)
public class MovieResource {
@RestClient
SwapiService swapiService;
@GET
public List<MovieDTO> getMovie(@QueryParam("year") String year) {
if (year != null) {
return Movie.<Movie>findByYear(Integer.parseInt(year)).stream()
.map(movie -> MovieDTO.of(movie, swapiService.getFilmById(String.valueOf(movie.id))))
.collect(Collectors.toList());
} else{
return Movie.<Movie>listAll().stream()
.map(movie -> MovieDTO.of(movie, swapiService.getFilmById(String.valueOf(movie.id))))
.collect(Collectors.toList());
}
}
@Transactional
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response newMovie(Movie movie) {
movie.id = null;
movie.persist();
return Response.status(Response.Status.CREATED).entity(movie).build();
}
}
Invoke the endpoint
You can check your new implementation using a REST client by pointing your browser to http://localhost:8080/movie?year=1980
You can also run the following command:
curl -w '\n' localhost:8080/movie?year=1980
[
{
"title": "The Empire Strikes Back",
"releaseDate": "1980-05-17",
"episode_id": 5,
"opening_crawl": "It is a dark time for the\r\nRebellion. Although the Death\r\nStar has been destroyed,\r\nImperial troops have driven the\r\nRebel forces from their hidden\r\nbase and pursued them across\r\nthe galaxy.\r\n\r\nEvading the dreaded Imperial\r\nStarfleet, a group of freedom\r\nfighters led by Luke Skywalker\r\nhas established a new secret\r\nbase on the remote ice world\r\nof Hoth.\r\n\r\nThe evil lord Darth Vader,\r\nobsessed with finding young\r\nSkywalker, has dispatched\r\nthousands of remote probes into\r\nthe far reaches of space....",
"director": "Irvin Kershner",
"producer": "Gary Kurtz, Rick McCallum"
}
]