[improve][misc] PIP-472: Migrate from javax.* to jakarta.* APIs#25912
Merged
Conversation
Implements PIP-472. Completes the javax.* -> jakarta.* namespace migration so Pulsar 5.0 LTS ships on clean Jakarta APIs. Dependencies (gradle/libs.versions.toml): - Jersey 2.42 -> 3.1.10; jakarta.ws.rs-api 2.1.6 -> 3.1.0; jakarta.annotation-api -> 2.1.1; jakarta.xml.bind-api -> 4.0.2; jakarta.validation-api -> 3.0.2; jakarta.activation-api -> 2.1.3 (impl -> org.eclipse.angus:angus-activation). - Added jetty-ee10-* alongside the retained jetty-ee8-*; added jakarta.servlet:jakarta.servlet-api:6.0.0 alongside the retained javax.servlet. - jackson-jaxrs-json-provider -> jackson-jakarta-rs-json-provider; Prometheus simpleclient_servlet -> simpleclient_servlet_jakarta. Source: - javax.ws.rs.* -> jakarta.ws.rs.* (Jersey 3 REST tier). - javax.servlet.* -> jakarta.servlet.* for Pulsar's own code; broker/proxy/websocket Jetty wiring moved ee8 -> ee10 (removed .get() core-Handler bridges; ee8.nested Request connector lookup -> ServletContextRequest). - WebSocket endpoints migrated to Jetty 12's native WebSocket API (org.eclipse.jetty.websocket.api.Session.Listener / Callback). AdditionalServlet plugin SPI (backward compatible): - Added AdditionalServletType.JAKARTA_SERVLET; broker/proxy route JAKARTA_SERVLET handlers to Jetty ee10 and keep routing legacy JAVAX_SERVLET handlers to the retained ee8 environment, so existing javax.servlet plugins keep working. Shading: client-shade-conventions and localrun-shaded relocations javax.* -> jakarta.*; renamed shaded META-INF/services javax.ws.rs.* files to jakarta.ws.rs.*. Swagger 1.x -> Swagger Core 2.x is deferred to a follow-up (decoupled, compile-/doc-only). See pip/pip-472-notes.md for discovered state, decisions and deviations. Assisted-by: Claude Code (Opus 4.8)
…e jetty-upgrade modules The global swap to simpleclient_servlet_jakarta broke jetty-upgrade/*-prometheus-metrics (io.prometheus.client.exporter.MetricsServlet), which legitimately stay on the javax Prometheus servlet. No Pulsar code uses the jakarta variant. Assisted-by: Claude Code (Opus 4.8)
- distribution/{server,shell} LICENSE.bin.txt: jersey 2.42->3.1.10, hk2 2.6.1->3.0.6,
jackson-jaxrs->jackson-jakarta-rs, jakarta API version bumps, Angus activation impl,
hk2.external jakarta.inject -> jakarta.inject:jakarta.inject-api, jetty ee8->ee10 jars
(ee8-servlet retained), + jakarta.servlet/CDI/interceptor API jars.
- BrokerAdditionalServletTest / ProxyAdditionalServletTest: declare JAKARTA_SERVLET so
the jakarta servlets route to the ee10 environment; drop ee8.nested.Request casts.
- CounterBrokerInterceptor: read path/status via the jakarta servlet API instead of
casting to Jetty's Response/ee8.nested.Response.
Assisted-by: Claude Code (Opus 4.8)
…CENSE It is still bundled transitively (the javax.activation JAF impl) alongside the new org.eclipse.angus:angus-activation; both must be listed. Assisted-by: Claude Code (Opus 4.8)
…ntime dep - Allow %2F-encoded path separators in admin/REST URLs: Jetty 12 ee10 rejects ambiguous URIs at the servlet layer (independent of the connector UriCompliance), which broke admin operations on topics whose names contain encoded separators. Enable ServletHandler.setDecodeAmbiguousURIs(true) in broker WebService, proxy WebServer and functions WorkerServer. - Provide javax.xml.bind:jaxb-api at runtime for pulsar-client-auth-athenz and pulsar-broker-auth-athenz: the Athenz ZTS client shades jackson-module-jaxb-annotations which needs the javax.xml.bind package that jakarta.xml.bind-api 2.3.3 used to ship; the bump to 4.0.2 (real jakarta.xml.bind) removed it. Pulsar's own code uses jakarta.xml.bind. See pip/pip-472-notes.md for the full CI-failure triage (incl. the open transaction-unit-test server-side request-body over-read under Jetty 12 ee10). Assisted-by: Claude Code (Opus 4.8)
…offloader tests Motivation: The javax.* -> jakarta.* migration (PIP-472) removed the legacy javax EE APIs from the transitive classpath, breaking the tiered-storage offloader unit tests that exercise pre-jakarta third-party libraries (jclouds 2.6.0 and Hadoop): - BlobStoreBackedInputStreamTest: NoClassDefFoundError javax.xml.bind.JAXBException (jclouds ContextBuilder.build()). - FileSystemManagedLedgerOffloaderTest: Hadoop MiniDFSCluster's fully-javax NameNode web UI failed because the migration force-upgraded its jersey-*:2.46 -> 3.1.10 (jakarta), so org.glassfish.jersey.servlet.ServletContainer stopped being a javax.servlet.Servlet; cascading NoClassDefFoundErrors for javax.ws.rs, javax.annotation.Priority and javax.validation.Validator. Modifications: - tiered-storage/jcloud: runtimeOnly javax.xml.bind:jaxb-api:2.3.1 (jclouds needs the legacy javax.xml.bind at runtime). - tiered-storage/file-system: pin the legacy javax web stack for test configs only (mirrors the existing Jetty-9 force) - Jersey 2.46 + hk2 2.6.1, plus testRuntimeOnly legacy javax.ws.rs/annotation/validation APIs; runtimeOnly jaxb-api for Hadoop. - libs.versions.toml: add javax-ws-rs-api (javax.ws.rs:javax.ws.rs-api:2.1.1) alias. - pip-472-notes.md: document this fix and the definitive server-side root-cause of the remaining transaction multi-broker over-read. Production offloaders are NARs that bundle their own deps and use HDFS RPC (no web UI), so they are unaffected; these forces are scoped to test configurations. All tiered-storage offloader tests (90) pass locally. Assisted-by: Claude Code (Opus 4.8)
…under Jetty 12 ee10
Motivation:
After the javax->jakarta migration moved the broker web layer to Jetty 12 ee10 /
Jersey 3, every request whose broker enables a BrokerInterceptor failed admin calls
that have a body (e.g. clusters().createCluster(...)) with:
HTTP 400 Trailing token (of type START_OBJECT) found after value bound as ClusterDataImpl
This broke all TransactionTestBase subclasses (TransactionStablePositionTest,
TransactionBufferCloseTest, AdminApiTransactionMultiBrokerTest, TransactionEndToEndTest),
ExceptionsBrokerInterceptorTest, and the TestBrokerInterceptors integration test. The
Client Api unit group passed because it enables no interceptor - the key clue.
Root cause:
Enabling an interceptor activates PreInterceptFilter, which wraps the request in
RequestWrapper. RequestWrapper.getInputStream() returned a NEW ByteArrayInputStream on
every call. Under Jetty 12 ee10 + Jersey 3 the entity reader fetches getInputStream()
more than once; after consuming the body and reaching EOF, a later call returned a fresh
stream positioned at byte 0, so the next read yielded the body's own leading '{' again -
a START_OBJECT that the stricter Jackson jakarta-rs provider rejects (FAIL_ON_TRAILING_TOKENS).
The extra byte is the body's own first byte, never a real next-request byte, so on-the-wire
framing was never corrupted; the wrapper was simply not Servlet-contract-compliant.
Modifications (RequestWrapper):
- Return a single, stable ServletInputStream from getInputStream() (cache it), as required
by the Servlet contract; re-fetching after EOF now yields EOF, not the body again.
- Buffer the body lazily, so an interceptor that does not read the body (the common case)
never causes the body to be buffered and the underlying stream is consumed once, by Jersey.
- Bound the buffered read to Content-Length and report isFinished() correctly.
Verified locally: TransactionStablePositionTest (6), TransactionBufferCloseTest (4),
ExceptionsBrokerInterceptorTest, BrokerInterceptorTest and InterceptFilterOutTest all pass;
checkstyle + spotless clean.
Assisted-by: Claude Code (Opus 4.8)
Update the implementation notes CI iteration log: run 26700351491 (commit 8c70136) is fully green across all 39 jobs, completing Phase A of the javax->jakarta migration with end-to-end validation on lhotari/pulsar. Assisted-by: Claude Code (Opus 4.8)
….12.42 uses jakarta.xml.bind) Motivation: Earlier in this PR a temporary `runtimeOnly(libs.jaxb.api)` (javax.xml.bind:jaxb-api:2.3.1) was added to pulsar-client-auth-athenz and pulsar-broker-auth-athenz to satisfy Athenz 1.10.62, whose shaded jackson-module-jaxb-annotations referenced the legacy javax.xml.bind package (which the jakarta.xml.bind-api 4.0.2 bump no longer ships). master has since upgraded Athenz to 1.12.42 (apache#25905, 14e228c), which itself migrated to jakarta.xml.bind: athenz-auth-core now pulls org.glassfish.jaxb:jaxb-runtime:4.0.8. The legacy javax.xml.bind classes are therefore no longer needed on the Athenz runtime classpath, so the workaround is obsolete. Modifications: - Remove `runtimeOnly(libs.jaxb.api)` from pulsar-client-auth-athenz and pulsar-broker-auth-athenz (both build files revert to their pre-PR state). - De-Athenz the gradle/libs.versions.toml jaxb-api comment: the alias is retained because it is now used only by the tiered-storage offloaders (jclouds/Hadoop), which still reference legacy javax.xml.bind. - Update pip/pip-472-notes.md (issue 2, the CI iteration log) to record the upstream resolution. Verified: AuthenticationAthenzTest (8) and AuthenticationProviderAthenzTest (4) pass, and javax.xml.bind:jaxb-api is no longer on the Athenz runtimeClasspath (only the jakarta stack: jakarta.xml.bind-api 4.0.5 + org.glassfish.jaxb:jaxb-runtime 4.0.8). Assisted-by: Claude Code (Opus 4.8)
Drop pip/pip-472-notes.md (internal implementation working notes — not part of the upstream contribution) and the pip/pip-472.md "Implementation status / notes" pointer that referenced it, so the accepted PIP design document is left unchanged by this implementation PR. Assisted-by: Claude Code (Opus 4.8)
nodece
approved these changes
Jun 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PIP: PIP-472
Motivation
Implements PIP-472: Migrate from
javax.*tojakarta.*APIs.The upstream ecosystem (Jersey 3.x, Jetty 11+/12 ee10, Swagger Core 2.x, Spring 6, Hibernate Validator 7+)
requires the
jakarta.*namespace. Pulsar was in a half-migrated state: Jetty 12 was wired through itsee8(Servlet 4 /
javax.servlet) compatibility modules, Jersey was pinned at 2.x (javax.ws.rs), and thejakarta.*-named API artifacts were pinned to Jakarta EE 8 versions that still ship thejavax.*packages.This change completes the namespace migration so Pulsar 5.0 LTS ships on clean Jakarta APIs.
Modifications
Dependencies (
gradle/libs.versions.toml)2.42→3.1.10;jakarta.ws.rs-api2.1.6→3.1.0;jakarta.annotation-api→2.1.1;jakarta.xml.bind-api→4.0.2;jakarta.validation-api→3.0.2;jakarta.activation-api→2.1.3(impl switched to
org.eclipse.angus:angus-activation, the EE10 reference impl).jetty-ee10-*aliases alongside the retainedjetty-ee8-*; addedjakarta.servlet:jakarta.servlet-api:6.0.0alongside the retained
javax.servlet:javax.servlet-api:3.1.0.jackson-jaxrs-json-provider→jackson-jakarta-rs-json-provider; Prometheussimpleclient_servlet→simpleclient_servlet_jakarta.Source migration
javax.ws.rs.*→jakarta.ws.rs.*(169 files); broker/proxy/websocket/functions-worker REST tier on Jersey 3.javax.servlet.*→jakarta.servlet.*for Pulsar's own code; Jetty wiring (WebService,WebServer,websocket
ProxyServer, functionsWorkerServer) moved fromee8toee10(removed the.get()core-Handlerbridges;
ee8.nested.Request.getBaseRequest(...)→ServletContextRequest-based connector lookup).(
org.eclipse.jetty.websocket.api.Session.Listener/Callback).AdditionalServletplugin SPI (backward compatible)AdditionalServletType.JAKARTA_SERVLET. The broker (PulsarService/WebService) and proxy(
ProxyServiceStarter/WebServer) now routeJAKARTA_SERVLEThandlers to Jetty's ee10 environment and keeprouting legacy
JAVAX_SERVLEThandlers to the retained ee8 environment, so existingjavax.servletpluginskeep working without recompilation. Both environments coexist on the same Jetty server.
Shading / packaging
pulsar.client-shade-conventionsandlocalrun-shadedrelocations updatedjavax.*→jakarta.*; renamed thechecked-in
META-INF/services/org.apache.pulsar.shade.javax.ws.rs.*provider files to...jakarta.ws.rs.*.Swagger 1.x → Swagger Core 2.x is deferred to a follow-up (it is
io.swagger, compile-/doc-only, and decoupledfrom the namespace move); Swagger stays at 1.6.2 in this change.
Third-party
javax.*that intentionally remains (not Pulsar's own code — kept available where third-party libsstill reference the legacy packages):
MiniDFSCluster) still reference legacyjavax.xml.bind,javax.ws.rs,javax.annotationandjavax.validation; these are restored as runtime-/test-scoped deps, and thefile-system offloader test classpath is pinned to the legacy javax web stack (Jetty 9 / Jersey 2.46 / hk2 2.6.1).
async-http-client(thepulsar-client-adminHTTP client) pullscom.sun.activation:jakarta.activation:1.2.2(the legacy
javax.activationimpl, for multipart MIME-type detection), so it remains bundled.masterships Athenz1.12.42, which already usesjakarta.xml.bind(
athenz-auth-corepullsorg.glassfish.jaxb:jaxb-runtime:4.0.8).Verifying this change
This change is already covered by existing tests: the broker / proxy / client REST, servlet, websocket, broker
interceptor, transaction, function, and tiered-storage suites exercise the migrated Jetty 12 ee10 / Jersey 3 stack
end-to-end. A full green CI run on the author's fork (
lhotari/pulsar) covered 39/39 jobs — Build & License,all Broker unit groups (1–5), Client API/Impl, and the System/integration suites (including the broker-interceptor
and tiered-storage offloader suites).
spotlessCheck+checkstyleMain+checkstyleTestpass locally.A few Jetty 12 ee10 / Jersey 3 runtime issues surfaced during the migration and are fixed here:
%2F-encoded topic names —ServletHandler.setDecodeAmbiguousURIs(true)on theee10 servlet handlers (broker / proxy / functions-worker).
RequestWrapper.getInputStream()returned a fresh stream percall, so the ee10 / Jersey entity reader re-read the body's leading
{after EOF as a phantom trailing token;fixed by returning a single, Servlet-contract-compliant cached stream with lazy, Content-Length-bounded buffering.
Modifications above).
Does this pull request potentially affect one of the following parts:
If the box was checked, please highlight the changes
JAX-RS provider, and the activation impl (Angus); see Modifications.
pulsar-client-adminREST DTOs move tojakarta.*, and theAdditionalServletSPI gainsAdditionalServletType.JAKARTA_SERVLET. Auth / REST pluginsmust recompile against
jakarta.*; existingjavax.servletAdditionalServletplugins remain binary-compatible.