A1. Add dependencies

Add avaje-http-client and jackson-databind as a dependencies.

<dependency>
  <groupId>io.avaje</groupId>
  <artifactId>avaje-http-client</artifactId>
  <version>1.11</version>
</dependency>

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.12.3</version>
</dependency>
Gradle
dependencies {
  implementation 'com.fasterxml.jackson.core:jackson-databind:2.12.3'
  implementation 'io.avaje:avaje-http-client:1.11'
  ...
}

A2. Create HttpClientContext

We will use https://api.github.com as the base URL and use JacksonBodyAdapter to decode the json response.

Often we want to use a specific Jackson ObjectMapper and pass that to JacksonBodyAdapter. The default constructor for JacksonBodyAdapter uses an ObjectMapper with reasonable default options.

final HttpClientContext ctx = HttpClientContext.newBuilder()
  .baseUrl("https://api.github.com")
  .bodyAdapter(new JacksonBodyAdapter())
  .build();

A3. Add "record/data" class Contributor

We will use this to hold the response json results.

public static class Contributor {
  // just using public fields to get going ...
  public String login;
  public int contributions;
}

A4. Make the request

The below used avaje as the organisation and avaje-http as the repo.

final List<Contributor> contributors = ctx.request()
  .path("repos/avaje/avaje-http/contributors")
  .GET()
  .list(Contributor.class);

for (Contributor contributor : contributors) {
  System.out.println(contributor.login + " contributions:" + contributor.contributions);
}

All the code

All the code from the prior steps combined is:

package org.example;

import io.avaje.http.client.HttpClientContext;
import io.avaje.http.client.JacksonBodyAdapter;

import java.util.List;

public class Main {

  public static class Contributor {
    public String login;
    public int contributions;
  }

  public static void main(String[] args) {

    final HttpClientContext ctx = HttpClientContext.newBuilder()
      .baseUrl("https://api.github.com")
      .bodyAdapter(new JacksonBodyAdapter())
      .build();

    final List<Contributor> contributors = ctx.request()
      .path("repos/avaje/avaje-http/contributors")
      .GET()
      .list(Contributor.class);

    for (Contributor contributor : contributors) {
      System.out.println(contributor.login + " contributions:" + contributor.contributions);
    }
  }
}

Client API start

B1. Add dependencies

In addition to avaje-http-client and jackson-databind we need to add avaje-http-api and avaje-http-client-generator as dependencies.

Note that avaje-http-client-generator is the annotation processor that generates the implementation for our Client API.

<dependency>
  <groupId>io.avaje</groupId>
  <artifactId>avaje-http-client</artifactId>
  <version>1.11</version>
</dependency>

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.12.3</version>
</dependency>

<dependency>
  <groupId>io.avaje</groupId>
  <artifactId>avaje-http-api</artifactId>
  <version>1.11</version>
</dependency>

<!-- Annotation processor -->
<dependency>
  <groupId>io.avaje</groupId>
  <artifactId>avaje-http-client-generator</artifactId>
  <version>1.11</version>
  <scope>provided</scope>
</dependency>

If there are other annotation processors and they are specified via build / plugins / maven-compiler-plugin / annotationProcessorPaths then we add avaje-http-client-generator there instead.

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-compiler-plugin</artifactId>
  <configuration>
    <annotationProcessorPaths> <!-- All annotation processors specified here -->
      <path>
        <groupId>io.avaje</groupId>
        <artifactId>avaje-http-client-generator</artifactId>
        <version>1.11</version>
      </path>
      <path>
          ... other annotation processor ...
      </path>
    </annotationProcessorPaths>
  </configuration>
</plugin>

Gradle

dependencies {
    implementation 'com.fasterxml.jackson.core:jackson-databind:2.12.3'
    implementation 'io.avaje:avaje-http-client:1.11'

    implementation 'io.avaje:avaje-http-api:1.11'
    annotationProcessor 'io.avaje:avaje-http-client-generator:1.11'
    ...
}

B2. Create interface

Create a package org.example and create an interface called Github in that package like below:

package org.example;

import io.avaje.http.api.Client;
import io.avaje.http.api.Get;

import java.util.List;

@Client
public interface Github {

  @Get("/repos/{owner}/{repo}/contributors")
  List<Contributor> contributors(String owner, String repo);


  class Contributor {
    public String login;
    public int contributions;
  }
}

@Client

The interface marked with @Client it will be picked up by the avaje-http-client-generator annotation processor which will generate the implementation for the interface.

B3. IntelliJ IDEA

When using IntelliJ we can check if target / generated-sources / annotations is marked as a generated source.

Here target / generated-sources / annotations is currently not treated as a generated source (still orange)

We can see that Github$HttpClient.java has been generated but IntelliJ isn't aware of it as generated source yet.

 

On target / generated-sources / annotations

- right-mouse-click
- Mark Directory as
- Generated Sources Root

 

target / generated-sources / annotations is now marked as generated source

In IntelliJ IDE we will now be able to navigate from the interface to the implementation.

We can change the interface and recompile and the implementation will be regenerated to suit.

B4. Generated source

For the Github interface there would be generated source for Github$HttpClient found in target / generated-sources / annotations.

The generated source will look like:

package org.example.httpclient;

import io.avaje.http.api.*;
import io.avaje.http.client.HttpClientContext;
import java.util.List;
import org.example.Github;
import org.example.Github.Contributor;

@Generated("avaje-http-client-generator")
public class Github$HttpClient implements Github {

  private final HttpClientContext clientContext;

  public Github$HttpClient(HttpClientContext ctx) {
    this.clientContext = ctx;
  }

  // GET /repos/{owner}/{repo}/contributors
  @Override
  public List<Contributor> contributors(String owner, String repo) {
    return clientContext.request()
      .path("repos").path(owner).path(repo).path("contributors")
      .GET()
      .list(Contributor.class);
  }

}

B5. Use the interface

We can obtain the implementation via HttpClientContext create(T.class) method.

final Github github = httpClientContext.create(Github.class);

The full code example is below.

package org.example;

import io.avaje.http.client.HttpClientContext;
import io.avaje.http.client.JacksonBodyAdapter;

import java.util.List;

public class ApiMain {

  public static void main(String[] args) {

    final HttpClientContext httpClientContext = HttpClientContext.newBuilder()
      .baseUrl("https://api.github.com")
      .bodyAdapter(new JacksonBodyAdapter())
      .build();

    // obtain API ...
    final Github github = httpClientContext.create(Github.class);

    // use the API ...
    final List<Github.Contributor> contributors = github.contributors("avaje", "avaje-http");
    for (Github.Contributor contributor : contributors) {
      System.out.println(contributor.login+" contributions: "+contributor.contributions);
    }

  }
}

Logging

By default request response logging is built in (via RequestLogger) and we enable it via setting the log level to DEBUG or TRACE for io.avaje.http.client.RequestLogger

Example: Logback
<logger name="io.avaje.http.client.RequestLogger" level="trace"/>

DEBUG

Summary logging that includes the response status code and execution time is logged at DEBUG level.

TRACE

Logging that includes the request and response headers plus the body content is logged at TRACE level.

Next

Back to the main documentation