You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@knox.apache.org by Sandor Molnar <sm...@apache.org> on 2026/07/02 08:02:51 UTC

[DISCUSS] Handling JDK 17 -> 18+ Subject API changes (JEP 411) via a Multi-Release JAR

Hi all,

I'd like to open a discussion on how we handle the javax.security.auth.Subject API changes introduced by JEP 411, since it affects how Knox runs on newer JDKs and I think it's worth aligning as a community before we commit to an approach.


Background / driver
-----------------------
KNOX-3338 tracks the fact that on JDK 23+, Subject.getSubject(AccessController.getContext()) and Subject.doAs(...) are non-functional and throw UnsupportedOperationException. These were deprecated for removal in JDK 17 (JEP 411) and disabled in 23. Knox uses both in the request identity-propagation path (e.g. SubjectUtils.getCurrentSubject() and the various authentication filters), so Knox does not run correctly on JDK 23+ today.

We already had a proposed fix in PR #1277 that used reflection to call the JDK 18+ replacements (Subject.current() / Subject.callAs()) when available and fall back to the deprecated methods otherwise. It works, but I've closed it for now because I think we can do better than runtime reflection, and the reflection approach also surfaced some correctness concerns around migrating the doAs/getSubject call sites consistently (they must be migrated as a paired set — current() only observes identities established by callAs()).


The constraint
-----------------
The replacement APIs (Subject.current(), Subject.callAs()) only exist in JDK 18+. Knox is currently pinned to JDK 17 (maven.compiler.{source,target}=17, enforcer [17,18)), so the JDK-17 compile classpath simply doesn't have those methods, hence the reflection workaround.

Options I see
1. Reflection (as in PR #1277): works, but adds runtime reflection on a hot path and deprecation suppression, and is easy to get subtly wrong across the many call sites.
2. Bump the baseline to JDK 21: simplest code (call the new APIs directly), but drops JDK 17 support outright.
3. Multi-Release JAR (MRJAR): keep the JDK 17 baseline; ship a META-INF/versions/18 overlay for the handful of affected classes that calls current()/callAs() directly and type-safely. No runtime reflection, no deprecated calls on the modern path.

My preference: MRJAR (option 3)
- We only recently moved to a JDK 17 baseline, and we have not shipped a release on it yet (still 3.0.0-SNAPSHOT). Jumping straight to a higher baseline would effectively be a multi-step JDK bump within a single un-released cycle, which I'd rather avoid.
- There are also downstream distributions and existing deployments still standardized on JDK 17. Preserving the 17 baseline avoids disrupting those users while still making Knox correct on newer JDKs.
- MRJAR gets us both: run correctly on JDK 18-23+ and keep the 17 baseline, without reflection.

The trade-off is build complexity: MRJAR needs a JDK 18+ toolchain available at build time to compile the overlay (which means relaxing the enforcer's upper bound on the build JDK while keeping the target at 17), plus some maven-compiler-plugin wiring for the versioned source set. I think that's a reasonable one-time cost.

I'm happy to put up a PR prototyping the MRJAR layout for the affected classes if there's agreement on the direction.

Questions for the list:

- Any objection to MRJAR over bumping the baseline?
- Is anyone relying on / planning around a JDK baseline bump for 3.0.0 that I should factor in?

Thanks,
Sandor