Skip to main content

HTTP Transport Adapters

Overview​

The canonical RestClient in org.apache.juneau.rest.client is transport-agnostic. Every HTTP call is routed through an HttpTransport SPI, which is responsible for executing a fully-resolved TransportRequest and returning a TransportResponse.

RestClient  →  HttpTransport  →  (Apache HC 4.5 / Apache HC 5 / OkHttp / Jetty / JDK / Mock)

RestClient's POJO marshalling, remote proxies, interceptors, error-code handling, and logging are all independent of which transport is in use. Swapping transports requires one line of configuration.


Default Transport​

The built-in JavaHttpTransport — backed by the JDK's java.net.http.HttpClient — is the default. It is part of the juneau-rest-client artifact and requires no additional dependencies on Java 11+. For most applications it is the right starting point.

import org.apache.juneau.marshall.json5.*;
import org.apache.juneau.rest.client.*;

// No transport module needed — JavaHttpTransport is picked up automatically.
RestClient client = RestClient.builder()
.serializer(Json5Serializer.DEFAULT)
.parser(Json5Parser.DEFAULT)
.rootUrl("https://api.example.com")
.build();

JavaHttpTransport​

JavaHttpTransport wraps a JDK java.net.http.HttpClient. Create it with JavaHttpTransport.create() (default HttpClient) or JavaHttpTransport.builder(). The only builder option is httpClient(HttpClient), so every JDK-level setting (connect timeout, proxy, SSLContext, Authenticator, redirect policy, HTTP version, executor) is configured on the HttpClient you pass in:

import java.net.*;
import java.net.http.*;
import java.time.*;
import org.apache.juneau.rest.client.*;

HttpClient jdk = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(5))
.proxy(ProxySelector.of(new InetSocketAddress("proxy.example.com", 8080)))
.build();

RestClient client = RestClient.builder()
.transport(JavaHttpTransport.builder().httpClient(jdk).build())
.rootUrl("https://api.example.com")
.build();

JavaHttpTransport also honors RestRequest.timeout(Duration), which it applies as the JDK request timeout; see the matrix below for how the other transports treat it.


Transport Feature Matrix​

JavaHttpTransportApache HC 4.5Apache HC 5OkHttpJetty
Artifactjuneau-rest-clientjuneau-rest-client-apache-httpclient-45juneau-rest-client-apache-httpclient-50juneau-rest-client-okhttpjuneau-rest-client-jetty
Native client typejava.net.http.HttpClientCloseableHttpClient (HC 4.5)CloseableHttpClient (HC 5)OkHttpClientJetty HttpClient
Factorycreate() / builder()create() / builder()create() / builder()create() / builder()create() / builder() (both throw Exception)
Builder optionshttpClient(...)httpClient(...)httpClient(...)httpClient(...)httpClient(...), responseTimeoutMs(long)
Honors RestRequest.timeout(Duration)YesNoNoNoNo (use responseTimeoutMs, default 30 s)
Extra dependencyNoneHC 4.5HC 5okhttp-jvmjetty-client
URL policy (DNS pinning, redirect rules)AlwaysOnly when the transport creates its own clientOnly when the transport creates its own clientAlwaysOnly when the transport creates its own client

Notes:

  • All five transports are final classes with a final builder; there is no shared builder base class, so configuration that is specific to a native client always goes through that client's own builder and is handed over with httpClient(...).
  • For transports marked "Only when the transport creates its own client", supportsUrlPolicy() returns false as soon as you supply your own native client. The RestClient then refuses policy-covered @Remote / @Url requests through it. See NG Client Security.
  • To get a per-call timeout on a transport other than JavaHttpTransport, configure it on the native client (or, for Jetty, responseTimeoutMs).

Authentication Examples​

Authentication is configured on the native client and passed to the transport with httpClient(...). For bearer tokens and API keys, every transport can instead use the transport-independent RestClient.Builder.header(String, Supplier<String>), which resolves the value on each request:

RestClient client = RestClient.builder()
.rootUrl("https://api.example.com")
.header("Authorization", () -> "Bearer " + tokenStore.currentToken())
.build();

Basic credentials with each native client follow. Remember that supplying your own native client means you own its lifecycle and security configuration (see the policy note above).

JavaHttpTransport (JDK Authenticator):

HttpClient jdk = HttpClient.newBuilder()
.authenticator(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication("user", "secret".toCharArray());
}
})
.build();
RestClient client = RestClient.builder()
.transport(JavaHttpTransport.builder().httpClient(jdk).build())
.build();

Apache HttpClient 4.5 (CredentialsProvider):

BasicCredentialsProvider creds = new BasicCredentialsProvider();
creds.setCredentials(
new AuthScope("api.example.com", 443),
new UsernamePasswordCredentials("user", "secret"));

CloseableHttpClient hc = HttpClients.custom().setDefaultCredentialsProvider(creds).build();
RestClient client = RestClient.builder()
.transport(ApacheHc45Transport.builder().httpClient(hc).build())
.build();

Apache HttpClient 5 (CredentialsProviderBuilder):

var creds = CredentialsProviderBuilder.create()
.add(new AuthScope("api.example.com", 443), "user", "secret".toCharArray())
.build();

CloseableHttpClient hc = HttpClients.custom().setDefaultCredentialsProvider(creds).build();
RestClient client = RestClient.builder()
.transport(ApacheHc5Transport.builder().httpClient(hc).build())
.build();

OkHttp (Authenticator, which answers a 401 challenge):

OkHttpClient ok = new OkHttpClient.Builder()
.authenticator((route, response) -> response.request().newBuilder()
.header("Authorization", Credentials.basic("user", "secret"))
.build())
.build();
RestClient client = RestClient.builder()
.transport(OkHttpTransport.builder().httpClient(ok).build())
.build();

Jetty (AuthenticationStore):

org.eclipse.jetty.client.HttpClient jetty = new org.eclipse.jetty.client.HttpClient();
jetty.getAuthenticationStore().addAuthenticationResult(
new BasicAuthentication.BasicResult(URI.create("https://api.example.com"), "user", "secret"));
RestClient client = RestClient.builder()
.transport(JettyHttpTransport.builder().httpClient(jetty).build())
.build();

The Apache, OkHttp and Jetty imports are the native libraries' own packages (org.apache.http.* / org.apache.hc.client5.*, okhttp3.*, org.eclipse.jetty.client.*).


Transport Discovery​

RestClient discovers the transport to use in the following order of precedence:

  1. Explicit call — RestClient.Builder.transport(HttpTransport) always wins.
  2. ServiceLoader auto-discovery — each optional transport module registers an HttpTransportProvider via ServiceLoader. The highest-priority provider on the classpath is selected automatically when you add the module's Maven dependency.
  3. Built-in fallback — JavaHttpTransport is used when nothing else is found.

To set a transport explicitly:

import org.apache.juneau.rest.client.*;
import org.apache.juneau.rest.client.apachehttpclient45.*;

RestClient client = RestClient.builder()
.transport(ApacheHc45Transport.create())
.build();

Available Adapters​

ModuleArtifact IDNative ClientWhen to Choose
(built-in)juneau-rest-clientJDK java.net.http.HttpClientZero extra deps, Java 11+, good for most cases.
Apache HttpClient 4.5juneau-rest-client-apache-httpclient-45org.apache.httpcomponents:httpclient:4.5.xExisting HC 4.5 investment, NTLM/Kerberos, mature connection pooling.
Apache HttpClient 5juneau-rest-client-apache-httpclient-50org.apache.httpcomponents.client5:httpclient5:5.xNew projects wanting the latest Apache HC API; HTTP/2 via HC 5.
OkHttpjuneau-rest-client-okhttpcom.squareup.okhttp3:okhttp-jvm:5.xHTTP/2 + HTTP/1.1 fallback, Android, streaming, OkHttp interceptors.
Jettyjuneau-rest-client-jettyorg.eclipse.jetty:jetty-client:12.xApplications that already embed Jetty and want a shared lifecycle.

The HttpTransport SPI​

HttpTransport is a single-method interface:

public interface HttpTransport extends Closeable {
TransportResponse execute(TransportRequest request) throws TransportException;
}

TransportRequest carries the fully-resolved method, URI, headers, and optional body. TransportResponse carries the status code, reason phrase, response headers, and the raw InputStream body (or nothing for status-only responses). Juneau's marshalling layer converts between POJO types and these low-level streams.

Custom transports can be written by implementing this interface directly — useful for testing, protocol bridges, or non-HTTP backends:

HttpTransport myTransport = request -> TransportResponse.builder()
.statusCode(200)
.reasonPhrase("OK")
.body(new ByteArrayInputStream("{}".getBytes()))
.build();

RestClient client = RestClient.builder()
.transport(myTransport)
.build();

HttpTransport also has a supportsUrlPolicy() method that defaults to false. A transport that implements DNS pinning and the redirect rules must return true; otherwise the client refuses to send policy-covered @Remote / @Url requests through it. See NG Client Security.


Resource Lifecycle​

RestResponse — returned by RestRequest.run() — is the only Closeable in the user-facing per-call API. Always wrap it in try-with-resources to release the underlying connection promptly:

try (var resp = client.get("/users/123").run()) {
var user = resp.body().as(User.class);
}

// At application shutdown
client.close();

client.close() delegates to HttpTransport.close(), which releases connection pools, threads, and other resources held by the native HTTP client.


See Also​