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
| JavaHttpTransport | Apache HC 4.5 | Apache HC 5 | OkHttp | Jetty | |
|---|---|---|---|---|---|
| Artifact | juneau-rest-client | juneau-rest-client-apache-httpclient-45 | juneau-rest-client-apache-httpclient-50 | juneau-rest-client-okhttp | juneau-rest-client-jetty |
| Native client type | java.net.http.HttpClient | CloseableHttpClient (HC 4.5) | CloseableHttpClient (HC 5) | OkHttpClient | Jetty HttpClient |
| Factory | create() / builder() | create() / builder() | create() / builder() | create() / builder() | create() / builder() (both throw Exception) |
| Builder options | httpClient(...) | httpClient(...) | httpClient(...) | httpClient(...) | httpClient(...), responseTimeoutMs(long) |
Honors RestRequest.timeout(Duration) | Yes | No | No | No | No (use responseTimeoutMs, default 30 s) |
| Extra dependency | None | HC 4.5 | HC 5 | okhttp-jvm | jetty-client |
| URL policy (DNS pinning, redirect rules) | Always | Only when the transport creates its own client | Only when the transport creates its own client | Always | Only when the transport creates its own client |
Notes:
- All five transports are
finalclasses with afinalbuilder; 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 withhttpClient(...). - For transports marked "Only when the transport creates its own client",
supportsUrlPolicy()returnsfalseas soon as you supply your own native client. TheRestClientthen refuses policy-covered@Remote/@Urlrequests 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:
- Explicit call —
RestClient.Builder.transport(HttpTransport)always wins. ServiceLoaderauto-discovery — each optional transport module registers anHttpTransportProviderviaServiceLoader. The highest-priority provider on the classpath is selected automatically when you add the module's Maven dependency.- Built-in fallback —
JavaHttpTransportis 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
| Module | Artifact ID | Native Client | When to Choose |
|---|---|---|---|
| (built-in) | juneau-rest-client | JDK java.net.http.HttpClient | Zero extra deps, Java 11+, good for most cases. |
| Apache HttpClient 4.5 | juneau-rest-client-apache-httpclient-45 | org.apache.httpcomponents:httpclient:4.5.x | Existing HC 4.5 investment, NTLM/Kerberos, mature connection pooling. |
| Apache HttpClient 5 | juneau-rest-client-apache-httpclient-50 | org.apache.httpcomponents.client5:httpclient5:5.x | New projects wanting the latest Apache HC API; HTTP/2 via HC 5. |
| OkHttp | juneau-rest-client-okhttp | com.squareup.okhttp3:okhttp-jvm:5.x | HTTP/2 + HTTP/1.1 fallback, Android, streaming, OkHttp interceptors. |
| Jetty | juneau-rest-client-jetty | org.eclipse.jetty:jetty-client:12.x | Applications 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
- Next-Generation REST Client — full guide to the NG client API
- NG Client Security — per-transport DNS pinning and redirect guards
- juneau-rest-client Basics — the classic (Apache HC 4.5–based) stack