From 0c75ef81dc625e13bcb5cc2cb728ee16744f31be Mon Sep 17 00:00:00 2001 From: Lari Hotari Date: Wed, 6 May 2026 20:18:35 +0300 Subject: [PATCH 1/2] [pip] PIP-472: Migrate from javax.* to jakarta.* APIs --- pip/pip-472.md | 318 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 318 insertions(+) create mode 100644 pip/pip-472.md diff --git a/pip/pip-472.md b/pip/pip-472.md new file mode 100644 index 0000000000000..163853b86dae2 --- /dev/null +++ b/pip/pip-472.md @@ -0,0 +1,318 @@ +# PIP-472: Migrate from javax.* to jakarta.* APIs + +## Background Knowledge + +In 2017, Oracle transferred Java EE to the Eclipse Foundation, where it was renamed Jakarta EE. As part of that transfer, Oracle did not grant Eclipse the right to evolve the `javax.*` namespace; from Jakarta EE 9 onward (released 2020), every Jakarta EE specification moved its package prefix from `javax.*` to `jakarta.*`. The two namespaces are source- and binary-incompatible: an application can use either, but not both, for the same specification. + +Concretely, the following Pulsar-relevant specs moved from `javax.*` (Jakarta EE 8 and earlier) to `jakarta.*` (Jakarta EE 9+): + +| Specification | Old namespace | New namespace | +|---|---|---| +| Common Annotations (JSR 250) | `javax.annotation` | `jakarta.annotation` | +| Servlet | `javax.servlet` | `jakarta.servlet` | +| RESTful Web Services (JAX-RS) | `javax.ws.rs` | `jakarta.ws.rs` | +| Bean Validation | `javax.validation` | `jakarta.validation` | +| XML Binding (JAXB) | `javax.xml.bind` | `jakarta.xml.bind` | +| Activation Framework | `javax.activation` | `jakarta.activation` | +| Dependency Injection (JSR 330) | `javax.inject` | `jakarta.inject` | +| WebSocket | `javax.websocket` | `jakarta.websocket` | + +Independently of Jakarta EE, several `javax.*` packages are part of the JDK itself (not Jakarta EE specifications) and **stay in `javax.*` permanently**. They are out of scope for any "migrate to jakarta" effort: + +- `javax.crypto` (JCE) +- `javax.naming` (JNDI) +- `javax.net`, `javax.net.ssl` (JSSE) +- `javax.management` (JMX) +- `javax.security.auth.*` (JAAS — except for a few callbacks moved into Jakarta Security) +- `javax.sql` (DataSource APIs) +- `javax.tools` (Compiler API) +- `javax.xml.parsers`, `javax.xml.transform`, `javax.xml.xpath`, `javax.xml.stream` (JAXP — separate from JAXB) +- `javax.transaction.xa` + +### Apache Pulsar's current state + +Pulsar's build is Gradle, with all dependency versions managed centrally in `gradle/libs.versions.toml`. The minimum runtime is Java 21 (JDK 25 is also supported). The latest snapshot version is `5.0.0-M1-SNAPSHOT` and Pulsar 5.0 is the next LTS line per PIP-162. + +Today the codebase imports `javax.*` from approximately 1,620 Java files. Removing the JDK packages listed above leaves three real migration surfaces: + +| Package | Approx. files | Status | +|---|---|---| +| `javax.ws.rs` | 660 | Migrate to `jakarta.ws.rs` | +| `javax.servlet` | 368 | Pulsar's own code migrates to `jakarta.servlet`; usage in the `AdditionalServlet` plugin SPI surface is intentionally retained on `javax.servlet` (see *High-Level Design — Phase 1*) | +| `javax.annotation` (JSR 250 portion) | 48 | Migrate to `jakarta.annotation` | + +Pulsar's existing usage of `javax.security.*` (in `pulsar-broker-auth-sasl` and similar) is exclusively `javax.security.auth.callback`, `javax.security.auth.login`, `javax.security.auth.Subject`, and `javax.security.sasl.*` — all JDK-resident JAAS / SASL APIs that stay in `javax.*` and have no Jakarta counterpart. They are therefore out of scope. + +A partial migration is already visible in git history (e.g. commits replacing `javax.ws.rs-api` with `jakarta.ws.rs-api`, replacing `javax.annotation-api` with `jakarta.annotation-api`, and shading `jakarta.annotation` into shaded client JARs), and `gradle/libs.versions.toml` already declares Jakarta-named API artifacts. However, those Jakarta-named artifacts are pinned to Jakarta EE 8 versions, which still use the `javax.*` namespace — so the artifact rename has happened but the namespace has not. + +## Motivation + +1. **Upstream ecosystem has moved**. Jersey 3.x, Jetty 11+, Swagger Core 2.x, Hibernate Validator 7+, Spring 6, MicroProfile 5+ — all of them require `jakarta.*`. Pulsar can no longer pick up upstream bug fixes, performance improvements, security advisories, or compatibility with newer Java releases without leaving the `javax.*` namespace. + +2. **Pulsar is already partially mid-migration**. The Jakarta-named API artifacts are declared but pinned to javax-namespace versions, the partial replacement of individual artifacts is happening one PR at a time, and Jetty 12 is in use but configured with its `ee8-*` (javax-servlet compat) modules rather than `ee10-*`. The result is a half-migrated state that is harder to reason about and harder to extend than either endpoint. A coordinated migration ends this drift. + +3. **Pulsar 5.0 LTS is the right window**. Migration touches public surfaces (admin client REST DTOs, plugin/SDK annotations, function/connector base contexts). Once 5.0 ships and is supported as LTS, breaking those surfaces becomes very expensive: a major-version bump is the only legitimate path. Doing the migration before 5.0 cuts means the LTS line carries clean Jakarta APIs for its full lifecycle. + +4. **`javax.*` is functionally frozen**. Reference implementations in the `javax.*` namespace receive only critical security fixes; new features, performance work, and dependency updates land only in `jakarta.*`. Carrying `javax.*` through a multi-year LTS window means accumulating risk. + +5. **Direct `javax.servlet:javax.servlet-api:3.1.0` dependency**. Pulsar declares this dependency directly, which constrains every downstream module to Servlet 3.1 even though the deployed Jetty 12 runtime supports Servlet 6.0. Replacing the dependency unlocks newer Servlet APIs. + +## Goals + +### In Scope + +- **Source code namespace migration** for the Jakarta-EE-derived `javax.*` packages used in Pulsar's own code (`javax.ws.rs`, `javax.annotation` JSR-250 portion, `javax.validation`, `javax.xml.bind`, `javax.activation`, `javax.inject` where used, `javax.websocket` where used). `javax.servlet` is intentionally retained in the `AdditionalServlet` plugin SPI surface (see *High-Level Design*) and therefore not eliminated codebase-wide. Approximate scope: ~1,100 files (1,620 total `javax.*` imports minus ~476 in JDK-resident `javax.*` packages that stay in `javax.*` forever, minus the `javax.servlet` files that remain because of the AdditionalServlet retention). +- **Extend the `AdditionalServlet` SPI** so plugins can register `jakarta.servlet` handlers (preferred, routed to Jetty's ee10 environment) in addition to the existing `javax.servlet` registration path (legacy, routed to ee8). Both registration styles coexist; existing plugins keep working without recompilation. +- **Dependency upgrades** in `gradle/libs.versions.toml` to Jakarta-namespace versions of every affected library (Jersey 2.42 → 3.1.10, Swagger Core 1.6.2 → 2.2.27, add `jetty-ee10-*` alongside the retained `jetty-ee8-*`, add `jakarta.servlet:jakarta.servlet-api:6.0.0` alongside the retained `javax.servlet:javax.servlet-api`, bump the `jakarta-*` API versions to their Jakarta EE 10 namespace versions, swap `jackson-jaxrs-json-provider` for `jackson-jakarta-rs-json-provider`). +- **Shading and packaging cleanup** for shaded client/proxy/function JARs that currently include `javax.*` API classes (`pulsar-client-shaded`, `pulsar-client-admin-shaded`, `pulsar-client-all-shaded`, `pulsar-functions/localrun-shaded`, etc.). +- **LICENSE and NOTICE updates** to reflect any artifact replacements. +- **Migration guide** for plugin, connector, function, and embedding-API authors describing what changes they must make to recompile against Pulsar 5.0. +- **Land in Pulsar 5.0** before the LTS branch is cut. + +### Out of Scope + +- **JDK `javax.*` packages**: `javax.crypto`, `javax.naming`, `javax.net`, `javax.net.ssl`, `javax.management`, `javax.security.auth.*` (the JAAS portions), `javax.sql`, `javax.tools`, `javax.xml.parsers`, `javax.xml.transform`, `javax.xml.xpath`, `javax.xml.stream`, `javax.transaction.xa`. These are not Jakarta EE specifications and have no `jakarta.*` counterpart. +- **Removing `javax.servlet` support from the `AdditionalServlet` plugin SPI**. The SPI is extended to accept `jakarta.servlet` handlers (the preferred path going forward), but the existing `javax.servlet` handler registration continues to be supported so that existing AdditionalServlet plugins keep working without recompilation. Removing the `javax.servlet` registration path is a separate decision for a future PIP if desired. +- **Apache BookKeeper's own migration**. Pulsar embeds BookKeeper transitively. The Pulsar community should drive a Jakarta migration in BookKeeper 4.18 through the BookKeeper community (see *Alternatives*); the work itself happens in the BookKeeper repository, not as part of this PIP. +- **Wire-protocol or REST/HTTP shape changes**. The migration is purely a Java namespace change. HTTP request/response bodies, status codes, and admin REST API URLs are unchanged. +- **Functional behavioural changes** in any feature. Anything that changes broker, client, or function behaviour beyond the namespace move is out of scope and tracked separately. +- **A `javax.*` ↔ `jakarta.*` runtime compatibility shim** (e.g. Eclipse Transformer at JAR load time). See *Alternatives* for why this is rejected. + +## High Level Design + +The migration is executed in four phases. Each phase is mergeable on its own; the codebase remains buildable and shippable between phases. + +### Phase 0 — Preparation + +- Confirm the BookKeeper community's plans for jakarta migration; document any constraint this places on Pulsar's classpath. +- Add an OpenRewrite (or equivalent) automation tool to `build-logic` for the mechanical import-rename portion of the migration. The recipe of choice is `org.openrewrite.java.migrate.jakarta.JavaxMigrationToJakarta` from `rewrite-migrate-java`. Eclipse Transformer's source-mode is an alternative. +- Add a CI lint that prevents new `javax.ws.rs`, `javax.servlet`, `javax.annotation` (JSR 250 portion), or `javax.validation` imports from being introduced after Phase 1 lands. The lint is feature-flagged so it can be enabled per-module as the migration progresses. +- Inventory shaded client modules (`pulsar-client-shaded`, `pulsar-client-admin-shaded`, `pulsar-client-all-shaded`, `pulsar-functions/localrun-shaded`, `jclouds-shaded`, etc.) and document their current `javax.*` shading rules so Phase 3 has a checklist. + +### Phase 1 — Web stack migration with ee8/ee10 coexistence + +The web stack has two interlocked layers, each with its own coexistence story: + +1. **Servlet container (Jetty 12).** Jetty 12 supports running multiple Servlet API generations side by side, each in its own *environment*. Pulsar's broker, proxy, and websocket components move from `jetty-ee8-*` (Servlet 4 / `javax.servlet`) to `jetty-ee10-*` (Servlet 6 / `jakarta.servlet`). The `jetty-ee8-*` artifacts are **retained** alongside ee10 so that both environments share the same Jetty 12 server instance and connector pool. The `AdditionalServlet` SPI (`org.apache.pulsar.broker.web.plugin.servlet.AdditionalServlet*`) is extended so that plugins can register **either** `jakarta.servlet` handlers (preferred, routed to the ee10 environment) or `javax.servlet` handlers (legacy path, routed to the ee8 environment); both styles can coexist within a single broker. This is the Jetty 12 design intent for migration scenarios and is what unblocks gradual ecosystem migration without breaking existing AdditionalServlet plugins. + +2. **JAX-RS provider (Jersey).** Jersey 2 (`javax.ws.rs`) and Jersey 3 (`jakarta.ws.rs`) cannot share a single REST tier. The broker REST tier moves atomically to Jersey 3. Every JAX-RS resource class registered with the broker REST application — across `pulsar-broker`, `pulsar-broker-common`, `pulsar-client-admin`, `pulsar-websocket`, and `pulsar-proxy` — is updated in the same coordinated change to import `jakarta.ws.rs.*`. Modules outside the broker REST tier are unaffected by the Jersey switch and keep their `javax.*` imports until Phase 2. + +In `gradle/libs.versions.toml`: + +| Variable | Before | After | Note | +|---|---|---|---| +| `jersey` | `2.42` | `3.1.10` | | +| `swagger` | `1.6.2` | `2.2.27` | Group changes to `io.swagger.core.v3:swagger-*`; annotations move from `io.swagger.annotations.*` to `io.swagger.v3.oas.annotations.*` | +| `jakarta-ws-rs` | `2.1.6` | `3.1.0` | Jakarta EE 10 | +| `jakarta-annotation` | `1.3.5` | `2.1.1` | Jakarta EE 10 | +| `jakarta-validation` | `2.0.2` | `3.0.2` | Jakarta EE 10 | +| `jakarta-xml-bind` | `2.3.3` | `4.0.2` | Jakarta EE 10 | +| `jakarta-activation` | `1.2.2` | `2.1.3` | Jakarta EE 10 | +| `javax-servlet` | `3.1.0` | unchanged | **Retained** for the ee8 environment used by AdditionalServlet plugins | +| *new* `jakarta-servlet` | — | `6.0.0` | Servlet 6 / Jakarta EE 10 | + +The PIP targets **Jakarta EE 10** consistently across these specs (rather than mixing in EE 11) because EE 10 has the broadest tooling and runtime support today and matches the maturity of the third-party libraries Pulsar consumes. EE 11 is a candidate for a future follow-up. + +Plus aliases: + +- **Add** `jetty-ee10-*` library aliases for every `jetty-ee8-*` artifact currently in use. The `jetty-ee8-*` aliases are retained alongside ee10 so AdditionalServlet plugins keep working. Pulsar's own broker/proxy/websocket Jetty wiring uses ee10. +- **Replace** `jackson-jaxrs-json-provider` (`com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider`) with `jackson-jakarta-rs-json-provider` (`com.fasterxml.jackson.jakarta.rs:jackson-jakarta-rs-json-provider`) at the existing Jackson 2.21.3 version (Jackson publishes both variants under the same release). +- **Add** `jakarta.servlet:jakarta.servlet-api:6.0.0` as a new servlet-api alias **alongside** the retained `javax.servlet:javax.servlet-api:3.1.0`. + +Phase 1 lands as one coordinated PR that: + +- Updates `gradle/libs.versions.toml` per the table above. +- Updates Jetty wiring in `WebService`, `ProxyServer`, and `WebSocketService` to start the ee10 environment alongside ee8. +- Extends the `AdditionalServlet` SPI so plugins can register `jakarta.servlet` handlers in addition to existing `javax.servlet` handlers; existing plugin entry points stay binary-compatible. +- Migrates every JAX-RS resource class participating in the broker REST tier (~239 files: ~140 in `pulsar-broker` + 34 in `pulsar-broker-common` + 30 in `pulsar-client-admin` + 17 in `pulsar-websocket` + 18 in `pulsar-proxy`). The mechanical `javax.ws.rs.*` → `jakarta.ws.rs.*` and Swagger 1 → 2 annotation renames in those files are driven by OpenRewrite. + +Phase 1 is large but atomic — Jersey cannot be half-migrated. + +### Phase 2 — Per-module sweep of non-REST-tier `javax.*` + +After Phase 1, the broker REST tier (Jersey, Swagger, the `javax.ws.rs` resource classes in `pulsar-broker`, `pulsar-broker-common`, `pulsar-client-admin`, `pulsar-websocket`, and `pulsar-proxy`) is on jakarta. What remains is the non-REST `javax.*` usage scattered across the rest of the codebase: `javax.annotation` (JSR 250), `javax.validation`, `javax.xml.bind`, `javax.activation`, `javax.inject`, and `javax.websocket` where used. + +These are independent of the JAX-RS / Jersey switch and can be migrated one module at a time. A series of mechanical PRs runs the OpenRewrite recipe over each module: + +- Test trees of all modules already swept in Phase 1 (`pulsar-broker/src/test`, `pulsar-broker-common/src/test`, etc.) — JAX-RS test code follows the production code. +- `pulsar-client-api` and remaining client modules (small file counts; mostly `@Generated`, `@PostConstruct`, validation annotations). +- Function and connector modules whose source code uses `javax.annotation` / `javax.validation` (NAR runtime is unaffected; this is a source rename only). +- Documentation tooling (`pulsar-docs-tools`). + +Each PR: + +- Runs the OpenRewrite recipe for that module's sources and tests. +- Manually reviews any non-mechanical adjustments. +- Enables the per-module Checkstyle import check that bans new `javax.*` imports for that module. + +### Phase 3 — Cleanup + +- Update shading rules: remove `javax.ws.rs`, `javax.annotation-api`, `javax.validation`, `javax.xml.bind`, `javax.activation` entries (all now Jakarta-namespaced); add equivalent `jakarta.*` entries where shading is still required. The `javax.servlet` shading rules remain because the ee8 environment is retained for `AdditionalServlet` plugin support. +- Update LICENSE and NOTICE files in shaded client distributions. +- Remove now-unused exclusions (e.g. `Exclude javax.annotation` rules in build files). +- Enable the codebase-wide Checkstyle import check. +- Land a migration guide for plugin authors in the Pulsar website docs (apache/pulsar-site). + +## Detailed Design + +### Dependency upgrade table + +| Group:Artifact | Current | Target | Notes | +|---|---|---|---| +| `org.glassfish.jersey.core:jersey-server` (and all `org.glassfish.jersey.*` artifacts) | 2.42 | 3.1.10 | Group ID unchanged; major bump moves namespace to `jakarta.ws.rs` | +| `io.swagger:swagger-annotations`, `io.swagger:swagger-core` | 1.6.2 | — | **Replace with** `io.swagger.core.v3:swagger-annotations`, `io.swagger.core.v3:swagger-jaxrs2` at 2.2.27. Annotation package renames from `io.swagger.annotations.*` to `io.swagger.v3.oas.annotations.*` | +| `org.eclipse.jetty.ee8:jetty-ee8-*` | 12.1.8 | unchanged | **Retained** alongside `jetty-ee10-*` to preserve `AdditionalServlet` plugin support | +| *new* `org.eclipse.jetty.ee10:jetty-ee10-*` | — | 12.1.8 | Pulsar's own broker/proxy/websocket Jetty wiring uses ee10 | +| `javax.servlet:javax.servlet-api` | 3.1.0 | unchanged | **Retained** for the ee8 environment used by AdditionalServlet plugins | +| *new* `jakarta.servlet:jakarta.servlet-api` | — | 6.0.0 | Servlet 6 / Jakarta EE 10 | +| `jakarta.ws.rs:jakarta.ws.rs-api` | 2.1.6 | 3.1.0 | 2.1.x is Jakarta EE 8 (javax namespace); 3.1.x is Jakarta EE 10 (jakarta namespace) | +| `jakarta.annotation:jakarta.annotation-api` | 1.3.5 | 2.1.1 | 2.x is Jakarta EE 10 namespace | +| `jakarta.validation:jakarta.validation-api` | 2.0.2 | 3.0.2 | 3.x is Jakarta EE 10 namespace | +| `jakarta.xml.bind:jakarta.xml.bind-api` | 2.3.3 | 4.0.2 | 4.x is Jakarta EE 10 namespace | +| `jakarta.activation:jakarta.activation-api` | 1.2.2 | 2.1.3 | 2.x is Jakarta EE 10 namespace | +| `com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider` | 2.21.3 | — | **Replace with** `com.fasterxml.jackson.jakarta.rs:jackson-jakarta-rs-json-provider:2.21.3` (same Jackson version) | +| `org.springframework:*` | 6.2.12 | unchanged | Already Jakarta-native | +| `org.apache.bookkeeper:*` | 4.17.3 | track | Pulsar community to drive a Jakarta migration in BookKeeper 4.18 (see *Alternatives*) | + +### Source code automation + +OpenRewrite recipe: + +``` +org.openrewrite.java.migrate.jakarta.JavaxMigrationToJakarta +``` + +This recipe handles the bulk of the namespace renames in source files, including imports and fully-qualified references. Manual review is required for: + +- Reflection / dynamic class loading using string class names (e.g. `Class.forName("javax.ws.rs.core.Response")`). +- Code generation templates and resource files (e.g. service-loader files under `META-INF/services/`). +- XML configuration files referencing fully-qualified class names. +- Comments and documentation strings that still mention `javax.*`. + +A grep pass at the end of Phase 2 finds any residual `javax.*` outside the JDK-allowed list. + +### Shaded clients + +Shaded modules currently rewrite some `javax.*` classes into Pulsar's shaded namespace to avoid classpath conflicts. After Phase 1 the shading rules need to: + +- Add equivalent rules for `jakarta.*` API classes that are bundled. +- Remove `javax.*` rules that no longer apply. +- Keep JDK-resident `javax.*` classes (`javax.crypto`, `javax.net.ssl`, etc.) un-shaded — they were never bundled and remain on the JDK. + +### CI lint + +Pulsar already runs Checkstyle in CI. The migration uses Checkstyle's `ImportControl` (or `IllegalImport`) module to prevent regressions, since extending an existing tool is cheaper than introducing a new Gradle plugin. The Jakarta-EE-spec packages below are added to the existing Checkstyle configuration and disallowed across the codebase, with two well-defined exceptions: + +- `javax.servlet` is allowed only in code paths that implement the `AdditionalServlet` SPI (where javax-servlet support is intentionally retained). +- `javax.annotation.processing` is always allowed (it is a JDK package, not Jakarta EE). + +Disallowed packages for Pulsar's own code: + +- `javax.ws.rs` +- `javax.servlet` (except in the `AdditionalServlet` plugin SPI implementation classes) +- `javax.annotation` (excluding `javax.annotation.processing`) +- `javax.validation` +- `javax.xml.bind` +- `javax.activation` +- `javax.inject` +- `javax.websocket` + +The check is enabled per-module as that module is swept (Phase 2), then turned on codebase-wide in Phase 3. + +## Public-facing Changes + +### Public API + +The migration's compile-time impact on downstream Java code is narrower than the file count suggests, because most extension points live in their own classloaders or are explicitly preserved. + +**Affected — these consumers must change imports and recompile:** + +- **Embedding consumers of `pulsar-client-admin`** that import REST DTOs and any of their JAX-RS / Bean Validation annotations directly from Pulsar's published JARs. Annotations like `@PathParam`, `@QueryParam`, `@Encoded`, the `Response` builder, and Bean Validation `@NotNull` / `@Size` / `@Min` / `@Max` move from `javax.*` to `jakarta.*`. +- **Authentication / Authorization plugins** (`AuthenticationProvider`, `AuthorizationProvider`) and any other plugin that contributes JAX-RS resources or sub-resources to the broker REST tier — these are loaded into the broker's REST classpath and must use `jakarta.ws.rs.*`. +- **OpenAPI / Swagger annotation consumers**: any plugin that uses Swagger annotations on its REST classes must move from `io.swagger.annotations.*` (Swagger 1.x) to `io.swagger.v3.oas.annotations.*` (Swagger 2.x). + +**Unaffected — these continue to work without recompilation:** + +- **`AdditionalServlet` plugins**: existing plugins that register `javax.servlet` handlers continue to work unchanged — the SPI's `javax.servlet` registration path is retained and routed to Jetty's ee8 environment. New plugins (and existing plugins that want to migrate) can register `jakarta.servlet` handlers via the extended SPI; those go to the ee10 environment. Both styles coexist. +- **Connectors and Functions**: NAR archives execute in isolated classloaders (`NarClassLoader`). The connector / function base contexts (`BaseContext`, `FunctionContext`, `SinkContext`, `SourceContext`) currently do not expose Jakarta-EE-spec types in their signatures, so existing 4.x NARs continue to work on a 5.0 broker as long as they don't directly call the affected Pulsar REST/auth SPIs. A connector / function NAR that internally uses `javax.ws.rs` for its own HTTP client is also unaffected, because nothing forces those NARs to share Pulsar's JAX-RS provider. + +**Wire protocol and REST API are unchanged.** Pulsar's binary protocol, REST API URLs, request bodies, response bodies, and status codes are identical before and after the migration. A 4.x client speaking to a 5.0 broker, or vice versa, behaves the same as it does today. + +### Binary Protocol + +No changes. + +### Configuration + +No new or removed configuration. A small number of Jersey/Jetty internal configuration keys may differ between Jersey 2.42/Jetty `ee8` and Jersey 3.1/Jetty `ee10`; any user-visible difference will be documented in the upgrade guide. + +### CLI + +No CLI changes. + +### Metrics + +No metric changes. + +## Monitoring + +No new monitoring is required. Existing health checks, latency metrics, and request-rate metrics on the broker, proxy, and WebSocket components continue to function unchanged. + +During the migration window, operators are advised to watch for any classpath errors of the form `NoClassDefFoundError: javax.ws.rs.*` or `NoClassDefFoundError: jakarta.ws.rs.*` in broker logs after upgrade — these indicate a third-party plugin compiled against the wrong namespace. + +## Security Considerations + +- Jersey 3.1 and Jetty 12 (with `ee10` modules) both receive active security maintenance; Jersey 2.x and Jetty's `ee8` family are increasingly only fixed for serious CVEs. Migration improves Pulsar's exposure to security fixes. +- Swagger Core 2.x receives active maintenance; 1.6.x is end-of-life. +- No new authentication, authorisation, or transport-security surfaces are introduced. JAAS (`javax.security.auth.*`) and JSSE (`javax.net.ssl`) remain on `javax.*` (they are JDK SPIs, not Jakarta EE). +- Plugin authors recompiling for the new Pulsar 5.0 must use updated dependencies; the migration guide explicitly calls out that plugins compiled against the old `javax.*` namespace will not load on a 5.0 broker, which is the desired (failing-loud) behaviour rather than a silent classpath conflict. + +## Backward & Forward Compatibility + +### Upgrade + +- Operators upgrading a broker from 4.x → 5.0: no special steps for the broker itself. Broker configuration is unchanged. +- **`AdditionalServlet` plugin authors**: existing `javax.servlet`-based plugins keep working without recompilation. New or migrating plugins can target `jakarta.servlet` through the extended SPI, which is the preferred path going forward. +- **Connector and function authors**: unaffected. NAR-isolated classloaders mean existing 4.x NARs continue to work on 5.0. +- **Authentication / authorization plugin authors and other authors of plugins that contribute JAX-RS resources to the broker REST tier**: must update `javax.ws.rs.*` / `javax.annotation.*` / `javax.validation.*` / `io.swagger.annotations.*` imports to the corresponding `jakarta.*` / `io.swagger.v3.oas.annotations.*` packages and recompile against Pulsar 5.0. +- **Embedding consumers of `pulsar-client-admin`**: Java applications that import REST DTOs / JAX-RS annotations from Pulsar's published JARs in-process must update their `javax.*` imports to `jakarta.*` and recompile. +- A migration guide for plugin authors will be published in the Pulsar website docs (apache/pulsar-site repo) covering the import-rename recipes (admin-client, REST plugin, Swagger annotation rename, OpenRewrite usage). + +### Downgrade / Rollback + +Wire protocol and REST API are unchanged, so a 5.0 broker can be replaced with a 4.x broker without rollback steps. + +### Pulsar Geo-Replication Upgrade & Downgrade/Rollback Considerations + +Geo-replication runs broker-to-broker over Pulsar's binary protocol, which this PIP does not change. A mixed-version geo-replicated cluster (some sites on 4.x, others on 5.0) replicates correctly. There are no special steps. + +## Alternatives + +1. **Stay on `javax.*` indefinitely.** + *Rejected.* The upstream ecosystem has moved; staying on `javax.*` means no new Jersey, Jetty, Swagger, Spring, Hibernate Validator features, performance work, or security fixes can be picked up. The cost grows over time. + +2. **Defer the migration to Pulsar 6.0.** + *Rejected.* Pulsar 5.0 is LTS. Carrying `javax.*` through 5.0 means the LTS line will require a major-version bump just to migrate. Doing it now keeps the LTS line clean for its full lifecycle. + +3. **Run-time `javax.*` ↔ `jakarta.*` bytecode transformation (Eclipse Transformer at load time).** + *Rejected.* Adds startup cost and operational complexity, masks classpath problems behind a translation layer that is itself a class of bugs, and does not help downstream consumers (their compile-time signatures still resolve against whichever side of the namespace the Pulsar artifacts publish). It also doesn't simplify the build. + +4. **Big-bang migration in a single feature branch.** + *Rejected.* The scope is ~1,100 files. A long-lived feature branch against master, which evolves daily, becomes unmaintainable to rebase. Phasing keeps master always-shippable and lets the work parallelise across contributors. + +5. **A `javax.*` → `jakarta.*` source compatibility shim layer.** + *Rejected.* Maintaining a permanent shim that re-exports `jakarta.*` types as `javax.*` (or vice versa) defeats the goal of removing the dual classpath, doubles the public surface, and is itself a source of subtle bugs. + +6. **Migrate Apache BookKeeper as part of this PIP.** + *Out of scope, but actively coordinated.* BookKeeper is a separate Apache project with its own community and release cadence, and a Jakarta migration in BookKeeper cannot land via a Pulsar PIP. The Pulsar and BookKeeper communities are closely linked, however, and the Pulsar community should drive a Jakarta migration in BookKeeper 4.18 through the BookKeeper community's normal process so that Pulsar 5.0 can ship on a BookKeeper release whose own classpath is also Jakarta-clean. If BookKeeper has not migrated by the time Pulsar 5.0 ships, BookKeeper's classpath will still contain `javax.*` symbols (BookKeeper's own code), but Pulsar's own code is clean either way. + +## General Notes + +The migration is heavily mechanical — most of the source-code change is import-rename driven by an automated recipe — but the dependency-upgrade and JAX-RS namespace switch in Phase 1 require careful coordination because Jersey 2 and Jersey 3 cannot coexist in the same REST tier. Phase 1 should be executed by contributors familiar with Jersey, Jetty, and Swagger initialisation in `pulsar-broker`. The Jetty 12 ee8/ee10 dual-environment configuration that preserves the `AdditionalServlet` plugin SPI is the key architectural decision that lets Phase 1 land without forcing all third-party AdditionalServlet plugins to recompile in lockstep. + +## Links + +* Mailing List discussion thread: *(populate after sending [DISCUSS] mail)* +* Mailing List voting thread: *(populate after vote opens)* +* Related: [PIP-421: Require Java 17 as the minimum for Pulsar Java client SDK](pip-421.md) +* Related: [PR 25100: Upgrade to Jetty 12.1.x](https://github.com/apache/pulsar/pull/25100) \ No newline at end of file From b1e07dc07b1c2cc6cf263f3d79b0da60b86d9980 Mon Sep 17 00:00:00 2001 From: Lari Hotari Date: Wed, 6 May 2026 20:33:43 +0300 Subject: [PATCH 2/2] Add discussion thread link --- pip/pip-472.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pip/pip-472.md b/pip/pip-472.md index 163853b86dae2..19d68eec8190c 100644 --- a/pip/pip-472.md +++ b/pip/pip-472.md @@ -312,7 +312,7 @@ The migration is heavily mechanical — most of the source-code change is import ## Links -* Mailing List discussion thread: *(populate after sending [DISCUSS] mail)* +* Mailing List discussion thread: https://lists.apache.org/thread/1crbw2y2zg89qsyyq7qnv3ldh640lpsy * Mailing List voting thread: *(populate after vote opens)* * Related: [PIP-421: Require Java 17 as the minimum for Pulsar Java client SDK](pip-421.md) * Related: [PR 25100: Upgrade to Jetty 12.1.x](https://github.com/apache/pulsar/pull/25100) \ No newline at end of file