REST Client

An atypical 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://fruityvice.com to get nutrition information about our fruits. The endpoint we’re interested in is this one:

  • api/fruit/{name}, which returns specific info about the given fruit name.

{
    "genus": "Musa",
    "name": "Banana",
    "id": 1,
    "family": "Musaceae",
    "order": "Zingiberales",
    "nutritions": {
        "carbohydrates": 22,
        "protein": 1,
        "fat": 0.2,
        "calories": 96,
        "sugar": 17.2
    }
}

Add the REST Client extension

Just open a new terminal window, and make sure you’re at the root of your tutorial-app project, then run:

  • Maven

  • Quarkus CLI

./mvnw quarkus:add-extension -D"extensions=quarkus-rest-client-reactive,quarkus-rest-client-reactive-jsonb"
quarkus extension add rest-client-reactive rest-client-reactive-jsonb
[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:1.4.2.Final:add-extension (default-cli) @ tutorial-app ---
✅ Adding extension io.quarkus:quarkus-rest-client
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.773 s
[INFO] Finished at: 2020-05-11T21:43:38-04:00
[INFO] ------------------------------------------------------------------------

Create FruityVice POJO

We need to create a POJO object that is used to unmarshal a JSON message from http://fruityvice.com.

Create a new FruityVice Java class in src/main/java in the com.redhat.developers package with the following contents:

package com.redhat.developers;

import jakarta.json.bind.annotation.JsonbCreator;

public class FruityVice {

    private String name;

    private Nutritions nutritions;

    FruityVice(String name, Nutritions nutritions) {
        this.name = name;
        this.nutritions = nutritions;
    }

    @JsonbCreator
    public static FruityVice of(String name, Nutritions nutritions) {
        return new FruityVice(name, nutritions);
    }

    public String getName() {
        return name;
    }

    public Nutritions getNutritions() {
        return nutritions;
    }

    public static class Nutritions {

        private double carbohydrates;

        private double calories;

        Nutritions(double carbohydrates, double calories) {
            this.carbohydrates = carbohydrates;
            this.calories = calories;
        }

        @JsonbCreator
        public static Nutritions of(double carbohydrates, double calories) {
                return new Nutritions(carbohydrates, calories);
        }

        public double getCarbohydrates() {
            return carbohydrates;
        }

        public double getCalories() {
            return calories;
        }

    }

}

Create FruityViceService

Now we’re going to implement a Java interface that mimics the remote REST endpoint.

Create a new FruityViceService 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;

@Path("/api/fruit")
@RegisterRestClient
public interface FruityViceService {

    @GET
    @Path("/{name}")
    @Produces(MediaType.APPLICATION_JSON)
    FruityVice getFruitByName(@PathParam("name") String name);

}

Configure REST Client properties

Add the following properties to your application.properties in src/main/resources:

com.redhat.developers.FruityViceService/mp-rest/url=https://fruityvice.com

Create FruitDTO

We’re going to enhance our FruitResource endpoint by creating a new FruitDTO POJO and add the additional information provided by the FruitViceService.

Create a new FruitDTO Java class in src/main/java in the com.redhat.developers package with the following contents:

package com.redhat.developers;

public class FruitDTO {

    private String name;

    private String season;

    private double carbohydrates;

    private double calories;

    private FruitDTO(String name, String season, double carbohydrates, double calories) {
        this.name = name;
        this.season = season;
        this.carbohydrates = carbohydrates;
        this.calories = calories;
    }

    public static FruitDTO of(Fruit fruit, FruityVice fruityVice) {
        return new FruitDTO(
            fruit.name,
            fruit.season,
            fruityVice.getNutritions().getCarbohydrates(),
            fruityVice.getNutritions().getCalories());
    }

    public String getName() {
        return name;
    }

    public String getSeason() {
        return season;
    }

    public double getCarbohydrates() {
        return carbohydrates;
    }

    public double getCalories() {
        return calories;
    }

}

Change FruitResource to use FruityViceService

Now that we have all the required classes, we can change FruitResource to get fruits by season and use our FruityViceService REST client via @RestClient annotation.

Change the FruitResource Java class in src/main/java in the com.redhat.developers package with the following contents:

package com.redhat.developers;

import org.eclipse.microprofile.rest.client.inject.RestClient;

import java.util.List;
import java.util.stream.Collectors;

import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;

@Path("/fruit")
public class FruitResource {



    @RestClient
    @Inject
    FruityViceService fruityViceService;

    @Transactional
    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Response newFruit(Fruit fruit) {
        fruit.id = null;
        fruit.persist();
        return Response.status(Status.CREATED).entity(fruit).build();
    }


    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public List<FruitDTO> fruits(@QueryParam("season") String season) {
        if (season != null) {
            return Fruit.findBySeason(season).stream()
                    .map(fruit -> FruitDTO.of(fruit, fruityViceService.getFruitByName(fruit.name)))
                    .collect(Collectors.toList());
        }
        return Fruit.<Fruit>listAll().stream()
                .map(fruit -> FruitDTO.of(fruit, fruityViceService.getFruitByName(fruit.name)))
                .collect(Collectors.toList());
    }

}

Invoke the endpoint

You can check your new implementation using a REST client by pointing your browser to http://localhost:8080/fruit?season=Summer

You can also run the following command:

curl localhost:8080/fruit?season=Summer
[
  {
    "calories": 0,
    "carbohydrates": 29,
    "name": "Blueberry",
    "season": "Summer"
  },
  {
    "calories": 0,
    "carbohydrates": 96,
    "name": "Banana",
    "season": "Summer"
  },
  {
    "calories": 0,
    "carbohydrates": 30,
    "name": "Watermelon",
    "season": "Summer"
  }
]