Skip to main content

Next-Generation REST Client

Which client?

juneau-rest-client (org.apache.juneau.rest.client.RestClient) is the recommended REST client. It is generally available in 10.0.0, transport-agnostic, and uses the JDK HttpClient by default. juneau-rest-client-classic (org.apache.juneau.rest.client.classic.RestClient, Apache HttpClient 4.5) stays supported and is not deprecated; it is the stable choice for existing users. See Choosing a REST Client.

Juneau 10.0 promotes the next-generation REST client and HTTP type stack to the canonical names under org.apache.juneau.rest.client.*, org.apache.juneau.rest.mock.*, and org.apache.juneau.http.*. The pre-existing Apache HttpClient 4.5–based stack ships unchanged under .classic packages and the new juneau-rest-client-classic Maven module — see V10 Migration Guide for the import-rename table.

See the guide: Call an existing REST API with a typed proxy

The next-generation stack:

  • Uses only JDK types in its API surface (org.apache.juneau.http). No org.apache.http.* on any signature.
  • Introduces an HttpTransport abstraction so you can plug in any HTTP client (Apache HC 4.5, Apache HC 5, JDK HttpClient, OkHttp, Jetty, or a custom transport) without changing user code.
  • Replaces 40+ Apache HC–specific builder methods on the classic RestClient.Builder with a small, focused builder. Transport-specific configuration lives on the transport builder, not on RestClient.Builder.

Package Layout​

PackageModuleRole
org.apache.juneau.httpjuneau-rest-commonJDK-native HTTP types (HttpHeader, HttpPart, HttpBody, status types, ~73 RFC-named headers, ~50 named response types, multipart, remote-proxy annotations). No httpcore dependency.
org.apache.juneau.rest.clientjuneau-rest-clientHttpTransport abstraction, canonical RestClient / RestRequest / RestResponse, interceptors, body converters, JUL-level-driven debug logging (RestClientDebugFormatter). No httpcore dependency.
org.apache.juneau.rest.mockjuneau-rest-mockMockHttpTransport and the canonical MockRestClient for in-process testing against a Juneau RestContext.
org.apache.juneau.http.classicjuneau-rest-common-classicPre-existing Apache HttpCore-based types (~170 classes). Kept for the classic stack.
org.apache.juneau.rest.client.classicjuneau-rest-client-classic (new)Pre-existing RestClient that implements org.apache.http.client.HttpClient. Apache HttpClient 4.5–based.
org.apache.juneau.rest.mock.classicjuneau-rest-mockPre-existing MockRestClient (extends classic RestClient and implements HttpClientConnection) plus its MockRestRequest / MockRestResponse / MockLogger / MockConsole / MockHttpClientConnectionManager helpers.

Transport Modules​

The JDK HttpClient–based JavaHttpTransport is built into the canonical juneau-rest-client artifact and is the default — it requires no third-party dependencies on Java 11+.

To swap to a different native HTTP client, add one of the optional transport modules below. Each ships only its own native client and registers a higher-priority HttpTransportProvider via ServiceLoader, which automatically takes over discovery. An explicit RestClient.builder().transport(...) call always wins.

ModuleArtifact IDNative client
Apache HttpClient 4.5juneau-rest-client-apache-httpclient-45org.apache.httpcomponents:httpclient:4.5.x
Apache HttpClient 5juneau-rest-client-apache-httpclient-50org.apache.httpcomponents.client5:httpclient5
OkHttpjuneau-rest-client-okhttpcom.squareup.okhttp3:okhttp-jvm
Eclipse Jetty clientjuneau-rest-client-jettyorg.eclipse.jetty:jetty-client

Each optional module ships three classes:

  • *Transport — the HttpTransport implementation, with static create() and builder() factories.
  • *TransportBuilder — a final builder whose httpClient(...) method accepts the native client, so all native-client–specific configuration (pooling, proxies, TLS, authentication) is done on that client's own builder (e.g. ApacheHc45Transport.builder().httpClient(HttpClients.custom()….build())).
  • *TransportProvider — ServiceLoader entry for auto-discovery.

See HTTP Transport Adapters for the full feature matrix, JavaHttpTransport configuration, and per-transport authentication examples.


Quick Start​

import org.apache.http.impl.client.*;
import org.apache.juneau.marshall.json5.*;
import org.apache.juneau.rest.client.*;
import org.apache.juneau.rest.client.apachehttpclient45.*;

// Pre-build the serializer/parser — configure marshalling externally
var serializer = Json5Serializer.create().build();
var parser = Json5Parser.create().build();

// Choose a transport explicitly and configure the underlying native client
HttpTransport transport = ApacheHc45Transport.builder()
.httpClient(HttpClients.custom().setMaxConnTotal(50).build())
.build();

RestClient client = RestClient.builder()
.transport(transport)
.serializer(serializer)
.parser(parser)
.rootUrl("https://api.example.com")
.build();

// Always use try-with-resources on the response — close() releases the connection
try (var resp = client.get("/users/{id}").pathData("id", 42).run()) {
var user = resp.body().as(User.class);
}

// At application shutdown
client.close();

Zero-Config Shortcut: create()​

RestClient.create() is a zero-config shortcut — it's shorthand for RestClient.builder().build(): an auto-discovered transport, no default serializer/parser, and no root URL. Reach for it when you just want a client to issue requests against absolute URLs with no marshalling configured up front:

RestClient client = RestClient.create();

try (var resp = client.get("https://api.example.com/users/{id}").pathData("id", 42).run()) {
var body = resp.body().asString();
}

Use RestClient.builder() — as shown above — whenever you need to configure a transport, serializer/ parser, root URL, default headers, interceptors, or any other builder option. create() and builder() are the only two idioms for obtaining an NG RestClient; create() is not a legacy alias for builder() (that shape belongs to the classic client, which is why porting classic code replaces RestClient.create() with RestClient.builder() — see Migration Path below).

Auto-Discovery​

If you don't pass an explicit transport, RestClient uses ServiceLoader to pick the highest-priority HttpTransportProvider on the classpath — this applies whether you configure the client via create() or via builder(). The following configures marshalling explicitly while still relying on transport auto-discovery:

RestClient client = RestClient.builder()
.serializer(Json5Serializer.DEFAULT)
.parser(Json5Parser.DEFAULT)
.rootUrl("https://api.example.com")
.build();

Key Design Differences from the Classic RestClient​

ConcernClassic RestClient (...client.classic)Canonical RestClient (...client)
Client constructioncreate() returns a Builder (call .build() to get a client)create() returns a built, ready-to-use client (the zero-config shortcut); builder() returns a Builder
HTTP integrationImplements org.apache.http.client.HttpClient; bound to Apache HC 4.5Composes an HttpTransport; transport-agnostic
Serialization modelMulti-language marshallers / format shortcuts (json(), xml(), …)One pre-built serializer/parser pair per client; no multi-language mode
Builder surface40+ Apache HC passthrough methodsSmall builder with only Juneau-specific concerns
LifecycleRestClient.close()RestClient.close() — same idiom; both implement Closeable
Per-request lifecycleRestRequest and RestResponse are both CloseableOnly RestResponse is Closeable; close() releases the connection
Header/part typesImplement org.apache.http.Header / NameValuePairImplement JDK-only HttpHeader / HttpPart interfaces
Schema validation on @Query/@Header/@FormData/@PathSupportedNot supported — toString() + CollectionFormat only

Resource Lifecycle​

RestResponse — returned by RestRequest.run() — is the single Closeable in the user-facing per-call API. Close it to release the underlying connection and stream:

// Correct — try-with-resources releases the connection even if run() or body() throws
try (var resp = client.get("/users").run()) {
return resp.body().as(UserList.class);
}

// At application shutdown
client.close();

RestRequest does not implement Closeable — it is a disposable fluent builder for a single call, and run() transfers ownership of the connection to the returned RestResponse.


Typed Response Header Accessors​

ResponseHeader (obtained via RestResponse.header(String)) has a small set of typed-wrapper convenience methods, mirroring the classic stack's asXxxHeader() family without pulling in any org.apache.http.classic.* types. Each is a thin parse over the header's raw string value using JDK types or the transport-neutral types already in juneau-rest-common:

MethodReturn typeAbsent / unparseable
asBoolean()Optional<Boolean>empty
asZonedDateTime()Optional<ZonedDateTime>empty (RFC-1123 HTTP date)
asUri()Optional<URI>empty
asEntityTag()Optional<EntityTag>empty (e.g. ETag)
asEntityTags()EntityTags (never null)EntityTags.EMPTY (e.g. If-Match)
asStringRanges()StringRanges (never null)empty (e.g. Accept-Encoding)
try (var resp = client.get("/users/123").run()) {
Optional<ZonedDateTime> lastModified = resp.header("Last-Modified").asZonedDateTime();
Optional<EntityTag> etag = resp.header("ETag").asEntityTag();
EntityTags ifMatch = resp.header("If-Match").asEntityTags();
}

These sit alongside the existing generic accessors (asInteger(), asLong(), asOptional(), asCsvList()); for anything not covered here, parse getValue() directly.


Declarative Remote-Proxy Features (Next-Gen)​

The @Remote / @RemoteOp proxy annotations (and the @RemoteGet / @RemotePost / @RemotePut / @RemoteDelete / @RemotePatch verb annotations) gain a family of declarative capabilities in the next-gen engine, obtained via client.remote(MyProxy.class). These are documented on their own page, NG Remote Proxies:

MyProxy proxy = client.remote(MyProxy.class);

Multipart File Uploads​

multipart/form-data is a first-class body type. Build a MultipartBody explicitly and send it like any other request body via RestRequest.body(HttpBody):

var mp = MultipartBody.builder()
.field("note", "hello")
.file("attachment", new File("report.pdf"), "application/pdf")
.build();

try (var resp = client.post("/cases/123/attachments").body(mp).run()) {
resp.assertOk();
}

RestRequest.formData(...) continues to produce application/x-www-form-urlencoded and should not be used for binary uploads. For a declarative alternative on remote proxies, see Declarative Multipart.


Logging​

Logging is framework-agnostic and driven by the JUL logger level. There is no .logger(...) builder method and no RestLogger type; configure the debug logger name (and optionally a RestClientDebugFormatter) on the builder, then raise the logger level:

RestClient client = RestClient.builder()
.serializer(Json5Serializer.DEFAULT)
.parser(Json5Parser.DEFAULT)
.debugLoggerName("myapp.http")
.build(); // uses the built-in JavaHttpTransport by default

// INFO = summary, FINE = + headers, FINEST = + bounded bodies (opt-in)
java.util.logging.Logger.getLogger("myapp.http").setLevel(java.util.logging.Level.FINE);

try (var resp = client.get("/users/123").run()) {
resp.assertOk();
}

Body dumping is off unless explicitly opted in, and credentials are redacted by default. See REST Client Logging and Debugging for the tier table, the secure-by-default rules, and request correlation.


Mock Transport (Serverless Testing)​

MockHttpTransport routes TransportRequest directly to a Juneau RestContext without network I/O. Use org.apache.juneau.rest.mock.MockRestClient for unit tests that exercise serialization, remote proxies, interceptors, and assertions:

import org.apache.juneau.rest.mock.MockRestClient;

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

For the legacy fluent surface (.json5(), .plainText(), .disableRedirectHandling(), etc.) use org.apache.juneau.rest.mock.classic.MockRestClient — see Mock REST Client Overview.


Migration Path​

The classic RestClient and its supporting juneau-rest-common Apache HttpCore types are not deprecated in 10.0; they moved to .classic packages and the new juneau-rest-client-classic module. Migration is opt-in and incremental:

  1. Stay on classic — keep using the same RestClient / MockRestClient types you have today; just adjust imports as described in V10 Migration Guide and add a Maven dependency on juneau-rest-client-classic. No behavioral change.
  2. Adopt the canonical stack — add the NG transport module for your preferred HTTP stack as a Maven dependency. Replace RestClient.create() with the canonical org.apache.juneau.rest.client.RestClient.builder() and adapt builder calls — some map 1:1 (rootUrl, default headers/query, interceptors, logger); marshaller, errorCodes, and executorService have no NG equivalent — configure marshalling via the discrete serializer(...)/parser(...) builder methods instead.
  3. Wrap each call's RestResponse in try-with-resources. Call close() on the client at application shutdown.
  4. If you customized Apache HttpClient through httpClientBuilder() / connectionManager() / defaultRequestConfig(), build the equivalent native CloseableHttpClient yourself and pass it to the transport builder's httpClient(...) method (e.g. ApacheHc45Transport.builder().httpClient(...)).
  5. If you relied on multi-language marshallers or per-request language overrides, create one RestClient per language.

The canonical stack will graduate to stable in a future release, at which point the classic stack may be deprecated and eventually removed.


See Also​