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
We can see that |
On
- right-mouse-click |
![]() |
![]() |
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