Skip to main content

Release 10.0.0: Microservice

This page is part of the Release 10.0.0 release notes. The overview page lists the major changes, the breaking changes and the API changes for the whole release.

juneau-microservice-jetty​

JettyMicroservice zero-config facade + bundled defaults​

juneau-microservice-jetty now ships a thin convenience facade, JettyMicroservice, that collapses the Microservice + JettyConfiguration bootstrap into a single static call, plus classpath defaults for jetty.xml and juneau.cfg. End result: stand up a Jetty-backed Juneau microservice with one Maven dependency and one @Bean Servlet.

public class App {
public static void main(String[] args) throws Exception {
JettyMicroservice.run(args, new RootResources()).join();
}
}

Bundled classpath defaults (all overridable):

  • jetty.xml — single plain-HTTP ServerConnector on $S{availablePort,10000}. No resourceBase / WebAppContext / ResourceHandler filesystem mount is registered (no static-file serving by default). Servlets are auto-mounted by JettyServerComponent from @Bean Servlet contributions.
  • juneau.cfg — sections for [Jetty] (port, jetty.xml reference, var resolution), [REST] (default theme), [Console] (interactive console enabled with the standard exit / restart / help / config commands), [Logging] (logDir), and [SystemProperties] (Jetty SLF4J adapter wiring).

Override semantics are unchanged: a consumer-supplied jetty.xml or juneau.cfg on the working directory or earlier on the classpath wins, as does a @Bean JettyServerFactory / @Bean JettySettings in the consumer's @Configuration.

The JettyMicroservice.run(...) facade has three overloads:

  • run(String[] args, Servlet rootServlet) — interactive console enabled.
  • run(String[] args, Servlet rootServlet, boolean startConsole) — explicit console opt-in/out.
  • run(String[] args, WritableBeanStore beanStore, boolean startConsole, Class<?>... configurations) — power-user form for callers contributing multiple @Bean Servlets, custom MicroserviceListeners, or a parent bean store.

juneau-my-jetty-microservice retired (BREAKING)​

The legacy juneau-my-jetty-microservice template module has been removed from the reactor. Consumers should migrate to the new JettyMicroservice facade in juneau-microservice-jetty — it delivers the same zero-config developer experience without requiring the consumer to copy a template project.

ThemeMenuItem widget removed (BREAKING)​

org.apache.juneau.rest.server.widget.ThemeMenuItem and its $W{ThemeMenuItem} variable are gone, with no @Deprecated stage. Its links used a ?stylesheet= query parameter that nothing in the server ever read, so it never switched a page's theme. Remove it from @HtmlDocConfig(widgets, navlinks); to change the stylesheet, set @HtmlDocConfig(stylesheet) or the REST/theme config entry. See Stylesheets.

juneau-microservice-jetty / juneau-microservice-tomcat​

Graceful shutdown + readiness gating (zero-downtime k8s rollouts)​

Both embedded-server microservice flavors now perform a zero-downtime graceful shutdown suitable for rolling Kubernetes deployments, sharing one identical shutdown contract.

On shutdown, a shared ReadinessState flag is flipped before the listening connector stops, so GET /readyz immediately returns 503 (state=OUT_OF_SERVICE) while a load balancer / Kubernetes is still routing. GET /livez and GET /healthz stay healthy, so the orchestrator does not kill the pod mid-drain. The server then waits up to a bounded stopTimeout (default 30s) for in-flight requests to drain — Jetty via server.setStopTimeout(...), Tomcat by pausing the connector and waiting for the active request count to reach zero — with an optional shutdownSettleDelay between the readiness flip and the connector stop.

  • New shared org.apache.juneau.rest.server.health.ReadinessState flag, consulted by HealthAggregator to gate the READY probe (liveness and the aggregate health surface are unaffected).
  • New stopTimeout / shutdownSettleDelay knobs on both JettySettings and TomcatSettings, also configurable via juneau.cfg ([Jetty] / [Tomcat] sections, in milliseconds). Precedence: programmatic settings > config-file entry > (Jetty only: jetty.xml value) > 30s default.
  • Bundled juneau.cfg documents the knobs and the recommended Kubernetes preStop hook + terminationGracePeriodSeconds pairing.
  • MCP subscriptions/listen streams no longer hold the drain open. Those SSE streams never finish on their own, so a stop with any subscriber connected used to wait out the full stopTimeout (30s by default). ReadinessState gained onOutOfService(key, callback), servlets can opt in via the new ReadinessStateAware interface, and AbstractMcpRestServlet uses it to close its subscriptions (new McpSubscriptionBroker.closeAll(), a no-op by default) as soon as shutdown begins, so each stream sends its terminal frame and completes.
@Bean
JettySettings jettySettings() {
return JettySettings.create()
.stopTimeout(Duration.ofSeconds(45))
.shutdownSettleDelay(Duration.ofSeconds(2))
.build();
}

See the new Graceful Shutdown & Readiness Gating topic page for details, including the Jetty/Tomcat parity table and Kubernetes deployment guidance.

Servlet filter registration for microservice apps​

A new ServletFilterRegistration bean mounts a jakarta.servlet.Filter on JettyServerComponent or TomcatServerComponent before the server starts, with URL patterns, explicit dispatcher types (REQUEST, FORWARD, ASYNC, ERROR) and a defined order relative to the auto-mounted AuthFilterChain. Previously only the REQUEST dispatch was reachable and only after start-up. The AuthFilterChain auto-mount now goes through the same ordered path. The petstore Jetty runner uses it for its console boundary and no longer wraps the root servlet.

On Tomcat, servlets mounted through TomcatServerComponent.addServlet are now async-capable (request.isAsyncSupported() is true), matching Jetty, so async @RestOp return types use the real async path instead of falling back to synchronous handling.

juneau-microservice-test (new module)​

@MicroserviceTest — whole-microservice JUnit 5 integration tests​

A new juneau-microservice-test module adds @MicroserviceTest, a JUnit 5 extension that boots a whole Microservice (config + lifecycle + embedded Jetty on an ephemeral port) for a test class — the standalone-microservice analog of Spring's @SpringBootTest.

  • Explicit SUT. Declare the system under test via @MicroserviceTest(configurations={...}) and/or a static Microservice.Builder microserviceBuilder() supplier method (no classpath scanning). The extension adds an ephemeral-Jetty Server (port 0) + JettyConfiguration automatically; Jetty for v1 (Tomcat pluggability is a follow-on).
  • Composes the existing @TestBean substrate (no parallel injection annotation). Default Mode INJECT installs overrides via Microservice.Builder.overridingBeanStore(...) before boot, so the service reads them from startup; Mode OVERLAY pushes/pops against the booted instance.
  • Parameter resolution for RestClient (bound to the booted root URL — the primary convenience), Microservice, WritableBeanStore, and the bound port.
  • Fresh instance per class, stopped cleanly in teardown (restart is unsupported); stop() restores process globals so multiple @MicroserviceTest classes are isolated within a JVM. The bound port is always read from the live ServerConnector.
  • Distinct from MockRestClient: use @MicroserviceTest for a real server/lifecycle over HTTP, MockRestClient for in-JVM single-resource tests.
  • This also added a small public composition entry point — JuneauBeanStoreExtension.discoverOverrides(...) — so other JUnit 5 extensions can reuse the @TestBean discovery without driving that extension's own lifecycle.

See the new Whole-Microservice Integration Tests (@MicroserviceTest) topic page.

juneau-microservice (formerly juneau-microservice-core)​

Per-microservice Config wired into Settings​

  • Microservice's constructor now installs a ConfigPropertySource for the microservice's own Config onto Settings.get(), and stop() removes it. Any @Value("${section/key}") injection — on REST resources mounted under the microservice, on listeners, or on any other bean built through BeanInstantiator — resolves against the microservice config without an explicit lookup call.

juneau.workingDir migrated to @Value​

  • The juneau.workingDir env-driven default on Microservice.Builder is now populated by an @Inject-annotated initializer (initWorkingDirFromEnv) that takes a @Value("${juneau.workingDir}") String parameter. The builder is now constructed via BeanInstantiator.of(Builder.class, beanStore).run(). Existing programmatic Microservice.create().workingDir(...) callers are unaffected — the @Inject initializer only sets workingDir when it has not already been assigned. See @Value Framework-Internal Adoption.

Module Renamed​

The juneau-microservice-core Maven artifact has been renamed to juneau-microservice. The aggregator pom is now juneau-microservice-parent. The Java package org.apache.juneau.microservice is unchanged. Update your pom.xml:

<!-- OLD -->
<dependency>
<groupId>org.apache.juneau</groupId>
<artifactId>juneau-microservice-core</artifactId>
<version>${juneau.version}</version>
</dependency>

<!-- NEW -->
<dependency>
<groupId>org.apache.juneau</groupId>
<artifactId>juneau-microservice</artifactId>
<version>${juneau.version}</version>
</dependency>

MicroserviceListener Fan-Out​

Microservice.start() / Microservice.stop() / Microservice.onConfigChange(...) now invoke every MicroserviceListener bean in the bean store, not just a single resolved primary listener.

  • onStart invokes listeners in registration order.
  • onStop invokes them in reverse registration order.

The builder-supplied .listener(...) value is registered into the bean store under the unnamed key and participates in the fan-out like any other @Bean MicroserviceListener. If no listeners are contributed by configurations and none is supplied via the builder, the default BasicMicroserviceListener is registered so the fan-out always has at least one no-op participant. This change enables companion modules (notably juneau-microservice-jetty) to drive server lifecycles entirely through standard MicroserviceListener hooks, without needing a Microservice subclass.

Inject-Aware Microservice​

Microservice is now inject-aware. Every microservice owns an internal WritableBeanStore (from juneau-commons) populated from @Configuration classes registered on the builder.

New builder methods:

  • Microservice.Builder.configurations(Class<?>...) — register one or more @Configuration classes whose @Bean methods/fields are processed at bootstrap.
  • Microservice.Builder.configurations(List<Class<?>>) — same, list form.
  • Microservice.Builder.beanStore(WritableBeanStore) — supply an externally-owned store instead of letting the microservice construct a fresh one. Useful for composing into a larger application or integrating with juneau-rest-server-springboot.

New accessor:

  • Microservice.getBeanStore() — never null. Contains the resolved Args, ManifestFile, Config, VarResolver, MicroserviceListener, the Microservice instance itself, plus everything contributed by registered @Configuration classes.

Lifecycle:

  • @PostConstruct callbacks fire on beans instantiated through the inject framework (e.g. via constructor injection in @Bean factory methods or BeanStore.instantiate(X)).
  • @PreDestroy callbacks fire on Microservice.stop(), which now closes the bean store and walks every resolved bean in LIFO order. Errors are logged at WARNING and do not abort the rest of the shutdown sequence.

Resolution priority for core types (Args, Config, etc.): explicit builder call wins, then @Bean-supplied value, then built-in default. The final resolved value is registered into the bean store so downstream @Bean factory methods see the same instance that the Microservice.getXxx() getter returns.

This change is fully additive — every existing builder method continues to work unchanged, and microservices that never call .configurations(...) behave identically to pre-10.0.0.

See Inject-Aware Microservice for the full guide.

View interface — engine-agnostic server-side render contract​

  • New org.apache.juneau.rest.server.view.View interface. Carries the data a templating engine needs to render a response: String getTemplateName(), Map<String, Object> getAttributes(), and a default Map<String, String> getResponseHeaders() seam. Engine-agnostic — concrete implementations live in per-engine bridge modules (juneau-rest-server-view-jsp, juneau-rest-server-view-thymeleaf, juneau-rest-server-view-mustache, and juneau-rest-server-view-freemarker all ship in 10.0.0; Phase B of the view-module lineup is complete).
  • Designed as the stable extension point for the new view-module family. New methods will be added as default-bodied where possible to preserve backward compatibility with downstream view impls.
@RestGet("/hello/{name}")
public View hello(@Path String name) {
return JspView.of("hello.jsp").attr("name", name); // JspView implements View
}

juneau-microservice-jetty​

JettyServerComponent.addFilter(...) + AuthFilterChain auto-mount​

JettyServerComponent now exposes two public filter-registration methods:

public JettyServerComponent addFilter(Filter filter, String urlPattern) { ... }
public JettyServerComponent addFilter(Filter filter, String... urlPatterns) { ... }

Both delegate to ServletContextHandler.addFilter(FilterHolder, String, EnumSet<DispatcherType>) for DispatcherType.REQUEST dispatches. Filters registered via these methods are mounted before any servlet, preserving filter-before-routing semantics.

onStart(Microservice) now scans the BeanStore for an AuthFilterChain bean and auto-registers it at /* before servlet discovery — no explicit addFilter(...) call is needed when the chain is declared as a @Bean.

JettyServerComponent env reads migrated to @Value​

  • JettyServerComponent previously called env("availablePort") and env("juneau.serverPort") to skip an empty fallback during port resolution. These reads are now @Value("${availablePort}") Optional<String> availablePortEnv and @Value("${juneau.serverPort}") Optional<String> serverPortEnv fields populated by ClassInfo.of(this).inject(this, store) inside onStart(Microservice). Behavior is unchanged. See @Value Framework-Internal Adoption.

HealthProbeConfiguration​

juneau-microservice-jetty now ships HealthProbeConfiguration, an opt-in @Configuration module that contributes:

  • @Bean Servlet healthProbeServlet() returning HealthMixin for auto-mount through JettyServerComponent.
  • @Bean HealthProbeSettings defaulting to a 1s indicator timeout (overridable via user bean).

End-state bootstrap:

Microservice.create()
.configurations(JettyConfiguration.class, HealthProbeConfiguration.class, AppConfig.class)
.build()
.start();

Transport-Agnostic REST Client and HTTP Stack​

Juneau 10.0.0 introduces a new REST client and HTTP type stack that decouples Juneau from Apache HttpClient 4.5 and promotes it to the canonical names. The pre-existing HC4.5-based stack continues to ship under explicit .classic packages and a new juneau-rest-client-classic module, so existing callers keep working without source changes other than adding a Maven dependency and importing from the new package.

The non-classic types in org.apache.juneau.rest.client.*, org.apache.juneau.rest.mock.*, and org.apache.juneau.http.* are generally available in 10.0.0. The classic stack remains supported and is not deprecated.

org.apache.juneau.http (in juneau-rest-common)​

A JDK-native replacement for the Apache HttpCore-based types previously in this package. No httpcore dependency in the canonical types — all interfaces (HttpHeader, HttpPart, HttpBody, HttpStatusLine, HttpResponseMessage) use JDK types only.

  • Typed header hierarchy (HttpStringHeader, HttpMediaTypeHeader, HttpMediaRangesHeader, HttpStringRangesHeader, HttpCsvHeader, HttpDateHeader, HttpUriHeader, HttpIntegerHeader, HttpLongHeader, HttpBooleanHeader, HttpEntityTagHeader, HttpEntityTagsHeader) with ~73 RFC-named header classes (Accept, ContentType, Authorization, …).
  • Entity / body types: HttpBodyBean, StringBody, ByteArrayBody, StreamBody, FileBody, MultipartBody (RFC 7578 streaming).
  • Status / response types: HttpStatusLineBean, BasicHttpResponse, BasicHttpException, ~50 named status classes (Ok, Created, NotFound, InternalServerError, …).
  • Remote-proxy annotations stay under org.apache.juneau.http.remote (@Remote, @RemoteGet, @RemotePost, @RemotePut, @RemotePatch, @RemoteDelete, @RemoteOp, @RemoteReturn).

The Apache HttpCore-based legacy types (~170 classes) moved to org.apache.juneau.http.classic.* in the same module, so existing juneau-rest-common consumers only need an import org.apache.juneau.http.classic.*; adjustment.

org.apache.juneau.rest.client (in juneau-rest-client)​

The transport abstraction and the new canonical RestClient / RestRequest / RestResponse. No org.apache.http.* dependency.

  • HttpTransport — single integration point replacing org.apache.http.client.HttpClient. Provides synchronous send(TransportRequest) and a sendAsync(...) default that transports can override for native async.
  • TransportRequest, TransportResponse (Closeable — owns connection-release hook), TransportHeader, TransportBody, TransportException — transport-layer DTOs using JDK types only.
  • HttpTransportBuilder / HttpTransportProvider SPI — ServiceLoader-based auto-discovery; passing a fully-built transport or transport builder is also supported.
  • RestClient — composes an HttpTransport; one serializer/parser/marshaller per client (no multi-language mode); fluent get/post/put/patch/delete/head/options/formPost/multipartPost API; remote proxies via getRemote(Class); explicit shutdown() (not Closeable).
  • RestRequest — the single Closeable in the user-facing API; resolves URI + path data + query, runs interceptors, serializes the body, calls the transport.
  • CollectionFormat enum (COMMA, PIPE, SPACE, TAB, REPEATED) for Iterable / array-valued params.
  • BodyConverter — pluggable request-body conversion; default chain handles HttpBody, MultipartBody, InputStream, byte[], Reader, File, PartList, and falls back to a SerializedBody that streams through the client's serializer.
  • RestCallInterceptor — onInit / onConnect / onClose lifecycle hooks (no Apache types).
  • Debug logging — see the client debug-logging entry above (JUL-level–driven RestClientDebugFormatter).

The legacy Apache HttpClient 4.5–based client moved to org.apache.juneau.rest.client.classic.* in the new juneau-rest-client-classic module — see "Classic Module Split" below.

Transport Implementations​

The JDK HttpClient–based JavaHttpTransport ships built into the canonical juneau-rest-client artifact and is the default — RestClient.create() works out of the box on Java 11+ with zero third-party dependencies (see an earlier change below).

To swap in a different native HTTP client, add one of the optional transport modules. Each registers a higher-priority HttpTransportProvider via ServiceLoader, so RestClient.create().build() automatically picks it up.

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
Eclipse Jetty clientjuneau-rest-client-jettyorg.eclipse.jetty:jetty-client

Each optional transport module ships *Transport, *TransportBuilder (with native client–specific configuration accessible via cast), and *TransportProvider. Common transport-builder configuration (connectTimeout, readTimeout, sslContext, hostnameVerifier, proxy, maxConnections, maxConnectionsPerRoute, followRedirects, …) lives on HttpTransportBuilder so most callers don't need to cast.

juneau-rest-mock — org.apache.juneau.rest.mock​

The canonical mock client now matches the new RestClient:

  • MockHttpTransport — routes TransportRequest directly to a Juneau RestContext without network I/O.
  • MockRestClient — composes MockHttpTransport and exposes the standard RestClient builder surface. Use MockRestClient.create(MyRestResource.class).marshaller(Json5.DEFAULT).build() for serverless integration tests.

The Apache HttpClient 4.5–implementing legacy MockRestClient moved to org.apache.juneau.rest.mock.classic.* alongside its MockRestRequest, MockRestResponse, MockHttpClientConnectionManager, MockLogger, and MockConsole helpers. The shared servlet-mock plumbing (MockServletRequest, MockServletResponse, MockHttpSession, MockPathResolver) stays at org.apache.juneau.rest.mock.* and is reused by both client flavors.

Classic Module Split​

The pre-existing Apache HttpClient 4.5–based REST client moved into a new sibling Maven module:

ArtifactContents
juneau-rest-client (canonical)Transport-agnostic NG RestClient. No org.apache.http.* dependency.
juneau-rest-client-classic (new)Legacy RestClient (extends org.apache.http.client.HttpClient); types live under org.apache.juneau.rest.client.classic.*. Depends on org.apache.httpcomponents:httpclient:4.5.14.

Both modules can be on the classpath simultaneously — they share juneau-rest-common but expose disjoint packages. The aggregator artifacts (juneau-shaded-rest-client, juneau-shaded-all, juneau-microservice, juneau-rest-mock, juneau-distrib) pull in both flavors so existing assemblies see no behavioral change.

juneau-rest-common split into juneau-rest-common + juneau-rest-common-classic​

Mirroring the juneau-rest-client split above, the juneau-rest-common module is now split into a pure transport-neutral module and a new sibling that owns every Apache HttpClient 4.5–compatible facade.

ArtifactContentsApache HttpClient 4.5 dependency?
juneau-rest-commonTransport-neutral types under org.apache.juneau.http.* (annotations, lazy header beans, remote-proxy meta, parts, entities, HttpHeaders factory), plus org.apache.juneau.http.remote.*.No. Zero org.apache.httpcomponents:* on the classpath.
juneau-rest-common-classic (new)Every type formerly at org.apache.juneau.http.classic.* — Apache HttpClient 4.5–style headers, parts, entities, resources, responses, the classic HeaderList, and the classic Remote*Meta proxy bridges. FQCNs unchanged — only the Maven coordinate moved.Yes. Depends on org.apache.juneau:juneau-rest-common and org.apache.httpcomponents:httpcore:4.4.16.

Why: juneau-rest-common was the last juneau-rest-* module forcing org.apache.httpcomponents:httpcore onto every downstream consumer (transports, microservices, examples, mock). Splitting it lets the canonical NG stack ship genuinely transport-neutral.

Backward compatibility:

  • Fully qualified class names are unchanged — org.apache.juneau.http.classic.header.EntityTag is still org.apache.juneau.http.classic.header.EntityTag. Source-compatible.
  • Code that already depended on juneau-rest-common and used a classic.* type now needs an additional <dependency> on juneau-rest-common-classic. The aggregator artifacts (juneau-rest-client-classic, juneau-rest-mock, juneau-microservice, juneau-shaded-rest-server, juneau-shaded-rest-client, juneau-shaded-all, juneau-distrib) already pull both modules in transitively, so most callers using those uber artifacts see no Maven change. (As of 10.0, juneau-rest-server and juneau-rest-server-springboot no longer pull juneau-rest-common-classic transitively — see the juneau-rest-server decoupled from Apache HttpClient 4.5 section below.)
  • EntityTag / EntityTags value types (no HC 4.5 dependency, despite the historical package) moved out of classic.header to org.apache.juneau.http.header.EntityTag / EntityTags. This is a source-incompatible change for any caller that imported them explicitly — see the v10.0 Migration Guide.
  • @Remote(headerList = ...) annotation method changed from Class<? extends HeaderList> to Class<?>. Existing user annotations like @Remote(headerList = MyHeaderList.class) continue to work; the classic RemoteMeta performs an instanceof HeaderList check at runtime before invoking. The default sentinel changed from HeaderList.Void.class to java.lang.Void.class.

Usage Examples​

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

// Pick a transport explicitly
RestClient client = RestClient.create()
.transportBuilder(ApacheHc45Transport.create()
.connectTimeout(Duration.ofSeconds(10))
.sslContext(mySSLContext)
.maxConnections(50))
.serializer(serializer)
.parser(parser)
.rootUrl("https://api.example.com")
.build();

// Or let the SPI auto-discover the best transport on the classpath
RestClient zeroConfig = RestClient.create()
.marshaller(Json5.DEFAULT)
.rootUrl("https://api.example.com")
.build();

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

// Multipart upload
try (var req = client.multipartPost("/upload")
.multipartField("album", "vacation")
.multipartFile("photo", Path.of("/tmp/beach.jpg"), "image/jpeg")) {
req.run();
}

// At application shutdown
client.shutdown();

Migration Path​

The classic RestClient / juneau-rest-common APIs remain fully supported. Migration is opt-in and incremental:

  1. Stay on classic — add a juneau-rest-client-classic dependency and replace import org.apache.juneau.rest.client.*; with import org.apache.juneau.rest.client.classic.*; (similar for org.apache.juneau.http.* → org.apache.juneau.http.classic.*). No behavior change.
  2. Migrate to the new stack — add a transport module (e.g. juneau-rest-client-apache-httpclient-45), keep import org.apache.juneau.rest.client.*;, and adapt builder calls. Most configuration (rootUrl, default headers/query/form/path, interceptors, marshaller) maps 1:1; per-Apache-HttpClient options (httpClientBuilder(), connectionManager(), defaultRequestConfig()) move onto the transport builder.
  3. Update tests — use org.apache.juneau.rest.mock.MockRestClient (canonical) for new tests; keep org.apache.juneau.rest.mock.classic.MockRestClient for the legacy fluent surface (.json5(), .plainText(), deprecated builder methods).

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

juneau-rest-server decoupled from Apache HttpClient 4.5​

Completes the HC 4.5 removal started by earlier changes. juneau-rest-server is now genuinely transport-neutral — it neither imports org.apache.http.* nor depends on juneau-rest-common-classic, so mvn -pl juneau-rest/juneau-rest-server dependency:tree | grep httpcomponents returns nothing. Pulling juneau-rest-server no longer drags org.apache.httpcomponents:httpcore:4.4.16 onto the consumer classpath.

New transport-neutral types in juneau-rest-common​

To give the server somewhere to migrate to, the following transport-neutral siblings of the classic HC 4.5 types were added in org.apache.juneau.http.*. They use only JDK types — no org.apache.http.* dependency.

New typeReplaces (classic)Notes
HttpRequestLine (interface) + request/HttpRequestLineBeanorg.apache.http.RequestLineMethod / URI / HttpProtocolVersion.
HttpProtocolVersion (record)org.apache.http.ProtocolVersionprotocol() / major() / minor() accessors plus parse(String).
header/HttpHeaderListorg.apache.juneau.http.classic.header.HeaderListMutable ordered list of HttpHeader with getFirst/getLast/getAll/forEach/copy plus case-sensitive and case-insensitive lookup.
part/HttpPartListorg.apache.juneau.http.classic.part.PartListMutable ordered list of HttpPart mirroring HttpHeaderList.
part/HttpStringPart, HttpBooleanPart, HttpIntegerPart, HttpLongPart, HttpDatePart, HttpUriPart, HttpCsvArrayPartorg.apache.juneau.http.classic.part.Basic*PartConcrete typed parts so RequestQueryParam / RequestFormParam / RequestPathParam / RequestHeader can keep their typed conversion ergonomics (.asInteger(), .asLong(), .asDate(), etc.).
header/HttpStringHeader, HttpBooleanHeader, HttpIntegerHeader, HttpLongHeader, HttpDateHeader, HttpEntityTagHeader, HttpEntityTagsHeader, HttpMediaTypeHeader, HttpMediaRangesHeader, HttpStringRangesHeader, HttpCsvHeader, HttpUriHeaderorg.apache.juneau.http.classic.header.Basic*HeaderAbstract bases with typed accessors; subclassed by the ~73 RFC-named header classes (Accept, ContentType, Authorization, …).
resource/HttpResourceBeanorg.apache.juneau.http.classic.resource.BasicResourceConcrete HttpResource implementation backed by HttpBody + List<HttpHeader>.

BasicHttpException (and therefore all ~62 named status subclasses in org.apache.juneau.http.response.* — BadRequest, Ok, NotFound, InternalServerError, …) gained the full classic fluent setter surface so server-side handlers that build responses via throw new BadRequest().setHeader(...).setReasonPhrase(...) keep working unchanged after the package swap:

  • setStatusCode(int), setReasonPhrase(String), setProtocolVersion(HttpProtocolVersion)
  • setHeader(HttpHeader), setHeader(String name, String value)
  • setHeaders(HttpHeader...), setHeaders(List<HttpHeader>)
  • setContent(HttpBody), setContent(String)
  • setLocale(Locale), setUnmodifiable()

Public API changes in juneau-rest-server​

The 10.0 release retypes the server-side public surface from org.apache.http.* onto the new transport-neutral types. The signature changes below are source-incompatible for any caller that imported the classic types directly:

MemberWasIs
RestRequest#getAllHeaders()org.apache.http.Header[]org.apache.juneau.http.HttpHeader[]
RestRequest#getRequestLine()org.apache.http.RequestLineorg.apache.juneau.http.HttpRequestLine
RestRequest#getProtocolVersion()org.apache.http.ProtocolVersionorg.apache.juneau.http.HttpProtocolVersion
RestRequest#addDefault(...) / RestOpContext.getDefaultRequestHeaders()org.apache.http.Header[] / HeaderListorg.apache.juneau.http.HttpHeader[] / HttpHeaderList
RequestHeader, RequestQueryParam, RequestFormParam, RequestPathParam, RequestHttpPart, BasicNamedAttributeextended org.apache.http.Header / org.apache.http.NameValuePairextend org.apache.juneau.http.HttpHeader / org.apache.juneau.http.HttpPart
BasicStaticFiles / BasicStaticFiles.Builder header collectionsorg.apache.http.Header[]org.apache.juneau.http.HttpHeader[]
FluentRequestLineAssertionover org.apache.http.RequestLineover HttpRequestLine
FluentProtocolVersionAssertionover org.apache.http.ProtocolVersionover HttpProtocolVersion
HttpStatusLine#getProtocolVersion()StringHttpProtocolVersion

Class renames in juneau-rest-server​

The four request-side part-list classes were renamed for naming consistency with the new HttpHeaderList / HttpPartList siblings in juneau-rest-common. No backward-compatibility shims are kept — callers must update imports and references.

WasIs
org.apache.juneau.rest.server.httppart.RequestFormParamsorg.apache.juneau.rest.server.httppart.RequestFormParamList
org.apache.juneau.rest.server.httppart.RequestHeadersorg.apache.juneau.rest.server.httppart.RequestHeaderList
org.apache.juneau.rest.server.httppart.RequestPathParamsorg.apache.juneau.rest.server.httppart.RequestPathParamList
org.apache.juneau.rest.server.httppart.RequestQueryParamsorg.apache.juneau.rest.server.httppart.RequestQueryParamList

The argument resolvers and RestPreCall / RestPostCall lifecycle hooks were updated to recognize the new names; any user method signature that referenced the old names needs to be updated.

Response processor rename​

org.apache.juneau.rest.server.processor.HttpEntityProcessor was renamed to HttpBodyProcessor. The class was already internally rewritten to operate on HttpBody instead of HC 4.5's HttpEntity, but the class name kept the legacy term until this release. The default response-processor chain registered by DefaultConfig was updated accordingly; any custom RestContext.responseProcessors(...) configuration that names HttpEntityProcessor.class needs to be updated to HttpBodyProcessor.class.

Backward-compatibility shim for legacy return types​

A small reflective bridge — org.apache.juneau.rest.server.processor.LegacyHttpResponseAdapter — was added so user @RestOp methods that still return classic org.apache.http.HttpResponse / org.apache.http.HttpEntity / org.apache.juneau.http.classic.resource.BasicResource instances keep working when juneau-rest-common-classic is on the runtime classpath, even though juneau-rest-server itself no longer compiles against those types. The bridge is invoked by HttpBodyProcessor and HttpResponseProcessor only when the returned object is a recognized legacy type; pure-canonical handlers never touch it.

Verification​

After this change:

  • mvn -pl juneau-rest/juneau-rest-server dependency:tree | grep httpcomponents returns nothing.
  • rg -n '^import\s+org\.apache\.http\.' juneau-rest/juneau-rest-server/src/main/java returns nothing.
  • rg -n '^import\s+org\.apache\.juneau\.http\.classic\.' juneau-rest/juneau-rest-server/src/main/java returns nothing.

JDK HttpClient is now the built-in default transport​

The JDK java.net.http.HttpClient–based transport (JavaHttpTransport) is now part of the juneau-rest-client artifact itself. The previously separate juneau-rest-client-java-httpclient module — which had no third-party dependencies and was never released — has been folded in and removed.

What this means for callers:

  • RestClient.create() works out of the box on Java 11+ with zero third-party dependencies. No transport module is required for a baseline HTTP client.
  • The four remaining sibling transport modules (juneau-rest-client-apache-httpclient-45, -apache-httpclient-50, -okhttp, -jetty) continue to register higher-priority providers via ServiceLoader and automatically take over discovery when added to the classpath.
  • Explicit RestClient.builder().transport(...) always wins over auto-discovery.
  • RestClient.discoverTransport() now falls back to JavaHttpTransport.create() when ServiceLoader returns no providers (a defensive measure for heavily-shaded uber-jars that strip META-INF/services files).

Package promotion:

JavaHttpTransport, JavaHttpTransportBuilder, and JavaHttpTransportProvider were promoted from org.apache.juneau.rest.client.javahttpclient.* to the canonical org.apache.juneau.rest.client.* package root. No previously released version exposed the javahttpclient package, so this is an internal restructuring of unreleased beta code — no migration steps required.

// Zero-config: uses the built-in JavaHttpTransport
try (var req = RestClient.create().get("https://api.example.com/users")) {
var users = req.run().as(User[].class);
}

// Explicit tuning of the JDK client
var transport = JavaHttpTransport.builder()
.httpClient(java.net.http.HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(5))
.build())
.build();
RestClient client = RestClient.builder().transport(transport).build();

// Or swap to a different transport entirely just by adding its module
// (no code change needed — ServiceLoader picks up the higher-priority provider)