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-HTTPServerConnectoron$S{availablePort,10000}. NoresourceBase/WebAppContext/ResourceHandlerfilesystem mount is registered (no static-file serving by default). Servlets are auto-mounted byJettyServerComponentfrom@Bean Servletcontributions.juneau.cfg— sections for[Jetty](port,jetty.xmlreference, var resolution),[REST](default theme),[Console](interactive console enabled with the standardexit/restart/help/configcommands),[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, customMicroserviceListeners, 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.ReadinessStateflag, consulted byHealthAggregatorto gate theREADYprobe (liveness and the aggregate health surface are unaffected). - New
stopTimeout/shutdownSettleDelayknobs on bothJettySettingsandTomcatSettings, also configurable viajuneau.cfg([Jetty]/[Tomcat]sections, in milliseconds). Precedence: programmatic settings > config-file entry > (Jetty only:jetty.xmlvalue) > 30s default. - Bundled
juneau.cfgdocuments the knobs and the recommended KubernetespreStophook +terminationGracePeriodSecondspairing. - MCP
subscriptions/listenstreams 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 fullstopTimeout(30s by default).ReadinessStategainedonOutOfService(key, callback), servlets can opt in via the newReadinessStateAwareinterface, andAbstractMcpRestServletuses it to close its subscriptions (newMcpSubscriptionBroker.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 astatic Microservice.Builder microserviceBuilder()supplier method (no classpath scanning). The extension adds an ephemeral-JettyServer(port 0) +JettyConfigurationautomatically; Jetty for v1 (Tomcat pluggability is a follow-on). - Composes the existing
@TestBeansubstrate (no parallel injection annotation). Default Mode INJECT installs overrides viaMicroservice.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@MicroserviceTestclasses are isolated within a JVM. The bound port is always read from the liveServerConnector. - Distinct from
MockRestClient: use@MicroserviceTestfor a real server/lifecycle over HTTP,MockRestClientfor in-JVM single-resource tests. - This also added a small public composition entry point —
JuneauBeanStoreExtension.discoverOverrides(...)— so other JUnit 5 extensions can reuse the@TestBeandiscovery 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 aConfigPropertySourcefor the microservice's ownConfigontoSettings.get(), andstop()removes it. Any@Value("${section/key}")injection — on REST resources mounted under the microservice, on listeners, or on any other bean built throughBeanInstantiator— resolves against the microservice config without an explicit lookup call.
juneau.workingDir migrated to @Value
- The
juneau.workingDirenv-driven default onMicroservice.Builderis now populated by an@Inject-annotated initializer (initWorkingDirFromEnv) that takes a@Value("${juneau.workingDir}") Stringparameter. The builder is now constructed viaBeanInstantiator.of(Builder.class, beanStore).run(). Existing programmaticMicroservice.create().workingDir(...)callers are unaffected — the@Injectinitializer only setsworkingDirwhen 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.
onStartinvokes listeners in registration order.onStopinvokes 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@Configurationclasses whose@Beanmethods/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 withjuneau-rest-server-springboot.
New accessor:
Microservice.getBeanStore()— never null. Contains the resolvedArgs,ManifestFile,Config,VarResolver,MicroserviceListener, theMicroserviceinstance itself, plus everything contributed by registered@Configurationclasses.
Lifecycle:
@PostConstructcallbacks fire on beans instantiated through the inject framework (e.g. via constructor injection in@Beanfactory methods orBeanStore.instantiate(X)).@PreDestroycallbacks fire onMicroservice.stop(), which now closes the bean store and walks every resolved bean in LIFO order. Errors are logged atWARNINGand 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.Viewinterface. Carries the data a templating engine needs to render a response:String getTemplateName(),Map<String, Object> getAttributes(), and adefault 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, andjuneau-rest-server-view-freemarkerall 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
JettyServerComponentpreviously calledenv("availablePort")andenv("juneau.serverPort")to skip an empty fallback during port resolution. These reads are now@Value("${availablePort}") Optional<String> availablePortEnvand@Value("${juneau.serverPort}") Optional<String> serverPortEnvfields populated byClassInfo.of(this).inject(this, store)insideonStart(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()returningHealthMixinfor auto-mount throughJettyServerComponent.@Bean HealthProbeSettingsdefaulting to a1sindicator 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 replacingorg.apache.http.client.HttpClient. Provides synchronoussend(TransportRequest)and asendAsync(...)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/HttpTransportProviderSPI —ServiceLoader-based auto-discovery; passing a fully-built transport or transport builder is also supported.RestClient— composes anHttpTransport; one serializer/parser/marshaller per client (no multi-language mode); fluentget/post/put/patch/delete/head/options/formPost/multipartPostAPI; remote proxies viagetRemote(Class); explicitshutdown()(notCloseable).RestRequest— the singleCloseablein the user-facing API; resolves URI + path data + query, runs interceptors, serializes the body, calls the transport.CollectionFormatenum (COMMA,PIPE,SPACE,TAB,REPEATED) forIterable/ array-valued params.BodyConverter— pluggable request-body conversion; default chain handlesHttpBody,MultipartBody,InputStream,byte[],Reader,File,PartList, and falls back to aSerializedBodythat streams through the client's serializer.RestCallInterceptor—onInit/onConnect/onCloselifecycle 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.
| Module | Artifact ID | Native client |
|---|---|---|
| Apache HttpClient 4.5 | juneau-rest-client-apache-httpclient-45 | org.apache.httpcomponents:httpclient:4.5.x |
| Apache HttpClient 5 | juneau-rest-client-apache-httpclient-50 | org.apache.httpcomponents.client5:httpclient5 |
| OkHttp | juneau-rest-client-okhttp | com.squareup.okhttp3:okhttp |
| Eclipse Jetty client | juneau-rest-client-jetty | org.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— routesTransportRequestdirectly to a JuneauRestContextwithout network I/O.MockRestClient— composesMockHttpTransportand exposes the standardRestClientbuilder surface. UseMockRestClient.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:
| Artifact | Contents |
|---|---|
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.
| Artifact | Contents | Apache HttpClient 4.5 dependency? |
|---|---|---|
juneau-rest-common | Transport-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.EntityTagis stillorg.apache.juneau.http.classic.header.EntityTag. Source-compatible. - Code that already depended on
juneau-rest-commonand used aclassic.*type now needs an additional<dependency>onjuneau-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-serverandjuneau-rest-server-springbootno longer pulljuneau-rest-common-classictransitively — see thejuneau-rest-serverdecoupled from Apache HttpClient 4.5 section below.) EntityTag/EntityTagsvalue types (no HC 4.5 dependency, despite the historical package) moved out ofclassic.headertoorg.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 fromClass<? extends HeaderList>toClass<?>. Existing user annotations like@Remote(headerList = MyHeaderList.class)continue to work; the classicRemoteMetaperforms aninstanceof HeaderListcheck at runtime before invoking. The default sentinel changed fromHeaderList.Void.classtojava.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:
- Stay on classic — add a
juneau-rest-client-classicdependency and replaceimport org.apache.juneau.rest.client.*;withimport org.apache.juneau.rest.client.classic.*;(similar fororg.apache.juneau.http.*→org.apache.juneau.http.classic.*). No behavior change. - Migrate to the new stack — add a transport module (e.g.
juneau-rest-client-apache-httpclient-45), keepimport 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. - Update tests — use
org.apache.juneau.rest.mock.MockRestClient(canonical) for new tests; keeporg.apache.juneau.rest.mock.classic.MockRestClientfor 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 type | Replaces (classic) | Notes |
|---|---|---|
HttpRequestLine (interface) + request/HttpRequestLineBean | org.apache.http.RequestLine | Method / URI / HttpProtocolVersion. |
HttpProtocolVersion (record) | org.apache.http.ProtocolVersion | protocol() / major() / minor() accessors plus parse(String). |
header/HttpHeaderList | org.apache.juneau.http.classic.header.HeaderList | Mutable ordered list of HttpHeader with getFirst/getLast/getAll/forEach/copy plus case-sensitive and case-insensitive lookup. |
part/HttpPartList | org.apache.juneau.http.classic.part.PartList | Mutable ordered list of HttpPart mirroring HttpHeaderList. |
part/HttpStringPart, HttpBooleanPart, HttpIntegerPart, HttpLongPart, HttpDatePart, HttpUriPart, HttpCsvArrayPart | org.apache.juneau.http.classic.part.Basic*Part | Concrete 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, HttpUriHeader | org.apache.juneau.http.classic.header.Basic*Header | Abstract bases with typed accessors; subclassed by the ~73 RFC-named header classes (Accept, ContentType, Authorization, …). |
resource/HttpResourceBean | org.apache.juneau.http.classic.resource.BasicResource | Concrete 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:
| Member | Was | Is |
|---|---|---|
RestRequest#getAllHeaders() | org.apache.http.Header[] | org.apache.juneau.http.HttpHeader[] |
RestRequest#getRequestLine() | org.apache.http.RequestLine | org.apache.juneau.http.HttpRequestLine |
RestRequest#getProtocolVersion() | org.apache.http.ProtocolVersion | org.apache.juneau.http.HttpProtocolVersion |
RestRequest#addDefault(...) / RestOpContext.getDefaultRequestHeaders() | org.apache.http.Header[] / HeaderList | org.apache.juneau.http.HttpHeader[] / HttpHeaderList |
RequestHeader, RequestQueryParam, RequestFormParam, RequestPathParam, RequestHttpPart, BasicNamedAttribute | extended org.apache.http.Header / org.apache.http.NameValuePair | extend org.apache.juneau.http.HttpHeader / org.apache.juneau.http.HttpPart |
BasicStaticFiles / BasicStaticFiles.Builder header collections | org.apache.http.Header[] | org.apache.juneau.http.HttpHeader[] |
FluentRequestLineAssertion | over org.apache.http.RequestLine | over HttpRequestLine |
FluentProtocolVersionAssertion | over org.apache.http.ProtocolVersion | over HttpProtocolVersion |
HttpStatusLine#getProtocolVersion() | String | HttpProtocolVersion |
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.
| Was | Is |
|---|---|
org.apache.juneau.rest.server.httppart.RequestFormParams | org.apache.juneau.rest.server.httppart.RequestFormParamList |
org.apache.juneau.rest.server.httppart.RequestHeaders | org.apache.juneau.rest.server.httppart.RequestHeaderList |
org.apache.juneau.rest.server.httppart.RequestPathParams | org.apache.juneau.rest.server.httppart.RequestPathParamList |
org.apache.juneau.rest.server.httppart.RequestQueryParams | org.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 httpcomponentsreturns nothing.rg -n '^import\s+org\.apache\.http\.' juneau-rest/juneau-rest-server/src/main/javareturns nothing.rg -n '^import\s+org\.apache\.juneau\.http\.classic\.' juneau-rest/juneau-rest-server/src/main/javareturns 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 viaServiceLoaderand automatically take over discovery when added to the classpath. - Explicit
RestClient.builder().transport(...)always wins over auto-discovery. RestClient.discoverTransport()now falls back toJavaHttpTransport.create()whenServiceLoaderreturns no providers (a defensive measure for heavily-shaded uber-jars that stripMETA-INF/servicesfiles).
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)