FELIX-6759 - Java 25 LTS support - #433
Conversation
- Try-out building framework and HTTP subprojects against java 25 to see what will break
- Use 25-ea (Early access)
- Update mockito-core to a version that has jdk 25 support via byte-buddy
- Update awaitility
- Disable jetty bundle
- Don't rely on snapshot build for jetty
|
can you also do a change in scr to trigger this test |
on detail |
Will do tomorrow 👍 |
- continue-on-error: true to allow building other modules after a failed one - Change SCR to trigger CI
|
might be interesting to du parallel build for matrix like here in osgi repo |
|
Results of the latest run, including SCR: SCR Framework HTTP |
I discussed this at https://www.mail-archive.com/dev%40felix.apache.org/msg57202.html But I didn't have any luck getting feedback on using a common solution between Equinox and Felix (and maybe others). Therefore I only integrated it into Equinox to get rid of the use of Unsafe for the URL singleton management. If someone from Felix would like to adopt the same strategy as I did in Equinox I think it would be good so the two framework's can live in the same JVM without cratering the URL singletons. |
|
Thanks @tjwatson, from my point of view we should indeed consider adopting the same approach as you mentioned in osgi/osgi#226 (comment). |
|
common solution between Equinox and Felix .... And springboot would be really best way to solve. |
I don't disagree but I have my doubts the SpringBoot URL factories will be open to play well with others. Worth a try if you already have a good contribution relationship with the Spring project to bring up the issue. But I first suggest we get Felix and Equinox to play well with each other to prove out the approach. |
|
In addition to the changes of removing the use of sun.misc.Unsafe, all code in Framework using SecurityManager should be removed, since SecurityManager has been completely disabled from Java 24 onwards. |
|
I disagree with your comment in Security manager. In all cases where Security Managemer is uses we check Existense before we use that. So no execution in Versions where Security Manager is remived. |
|
With the SecurityManager removal, it makes it difficult to support wide JDK LTS version ranges. I suggest making two supported branch streams with JDK LTS version alignment. There are two issues with trying to support a wide range of JDK versions:
The approach we are looking at taking in Apache ActiveMQ and Apache Karaf is to have branches with JDK supported ranges:: branch-a: Supported Java: JDK 17 to 21 (Apache Karaf is able to do JDK 11 to JDK 21) It does to appear that it is physically possible to mismatch JAAS API across JDK 11-25 or JDK 17-25 b/c of the JAAS API change. One side sets a ThreadLocal and uses doAs() and the newer API uses ScopedValue and runAs() methods on the Subject class. see: https://docs.oracle.com/en/java/javase/21/docs/api/java.base/javax/security/auth/Subject.html |
Is the class completely gone? I would be surprised because I would have expected loads of class not founds when running Equinox on Java 25, but we don't observe that. |
|
@mattrpav Just wanted to note that JDT has now new support for multi-release jars that maps nicely to what we have in maven. Multi-Release Jars are a perfect fit for such kind of support such JDK dependent changes, then one only need a |
|
@tjwatson I mispoke-- JDK 25 disables the SecurityManager by default and custom SecurityManages cannot be installed. @laeubi MRJ is a good idea, will check that out. Have you solved for how to do JDK-version-specific unit tests in a single Maven module? If so, I'd love to see a sample configuration. |
Let me know if you need any pointer or support, we would need something similar for equinox on the long run. Regarding testing the most useful these days is a matrix build what uses different native JVMs... of course one can write MR-Test cases as well its just a bit more setup. |
I have a distaste for MRJ. If we create a |
Yeah, the how-to-execute-tests is the issue I'm running into. Creating a MR-jar for compiling and packaging test classes seems straight-forward. The hang-up comes in as far as how to instruct the surefire plugin to execute those tests. ASFAIK, would require two separate Maven profiles (kludgy-- as the surefire configurations would need to be kept in sync b/w the two profiles) for listing include/exclude of class test names based on JDK version. Punting to use separate Maven modules by JDK version seems less than ideal. |
Anything specific? I'm looking at using a MRJ for activemq-client to use that for the Virtual Thread classes that need JDK 21+. |
It has been a while since I looked into them seriously. But the first blocker from me was source code debugging and what the source JAR looks like for the release. It was a nightmare to debug. Maybe all that is fixed by now. But it seemed far more simple to just choose the class to load myself in code. |
|
Many other projects settled on using a MR jar and a SubjectShim class that wraps the differences in the JAAS api for resolving the Subject |
|
Given this comment from @tjwatson, i doubt MR's are the way forward.
I would like to get some more opinions (and hopefully consensus) on this before we start work in either direction. |
fwiw: JDK recommends the two branch model too https://openjdk.org/jeps/14 |
…Java-25-LTS # Conflicts: # .github/workflows/maven-ci.yml # http/itest/pom.xml
Java SE 24 permanently disabled the Security Manager (JEP 486), so System.getSecurityManager() always returns null and none of the permission enforcement in Felix could ever run. Passing -Djava.security.manager=allow is now a fatal VM startup error, which prevented the framework tests from running on JDK 25 at all. The OSGi API types are kept (AdminPermission, ServicePermission, PackagePermission, BundlePermission, CapabilityPermission, AdaptPermission, Bundle.hasPermission, ProtectionDomain, BundleProtectionDomain and ext.SecurityProvider), so bundles referencing them still compile and link. Only the enforcement is removed. framework: - Felix.init() no longer installs a SecurityManager. Setting org.osgi.framework.security now fails fast instead of silently launching without the security the launcher asked for. - SecureAction: all 65 doPrivileged wrappers collapsed to their direct calls and the Actions dispatch class dropped (2113 -> 656 lines). - SecurityManagerEx no longer extends SecurityManager; it uses StackWalker, the supported replacement for getClassContext(), which yields frames in the same order. This raises felix.java.version to 9, the lowest level the module still builds at. - Permission checks removed from BundleImpl, BundleContextImpl, BundleWiringImpl, EventDispatcher, WovenClassImpl, StatefulResolver, ExtensionManager, ServiceRegistrationImpl, FrameworkStartLevelImpl, FrameworkWiringImpl, URLHandlersBundleStreamHandler and BundleProtectionDomain. Every removed branch was already unreachable without a SecurityManager, so behaviour is unchanged. - Dropped the two URLHandlersTest SecurityManager tests; without a SecurityManager they duplicated the two tests they wrapped. - asm-all 5.2 cannot read class files newer than Java 8 and broke the weaving hook tests; replaced with asm/asm-tree 9.8. framework.security: removed. The module existed only to implement the SecurityManager based SecurityProvider (PermissionAdmin, ConditionalPermissionAdmin). configadmin depends on the released 2.8.3 artifact, not on this module. scr, webconsole, gogo: removed the remaining getSecurityManager() gates and doPrivileged wrappers. Bumped gogo to mockito-core 5.18.0, as 5.17.0 ships a byte-buddy that cannot mock on JVM 25. CI: build on JDK 25 rather than 25-ea. Verified on JDK 25 against a JDK 21 control run on the same tree: framework, scr (170 tests), webconsole (23 tests) and gogo now have the same results on both JDKs. The residual failures are pre-existing Windows-only issues (a fixture filename containing invalid NTFS characters, and deleteDir file locking) that fail identically on JDK 21. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Follow-up to the SecurityManager removal, covering the non-SecurityManager problems that stop modules building or testing on JDK 25. Stale parent poms are the root cause of most of them. Several modules declare felix-parent 2.1-7 with relativePath ../pom/pom.xml, but the local pom is 10-SNAPSHOT, so the relativePath does not match and Maven silently resolves the old released parent from Maven Central instead. Those parents hardcode felix.java.version 6 or 7 (javac 25 rejects release < 8) and pin maven-surefire-plugin 2.x, which cannot parse the JDK 25 version string and dies with an NPE before running any test. Bumping the parent to felix-parent 9 fixes both at once, rather than overriding the compiler and plugin settings per module: - bundlerepository (2.1), configadmin (6), connect (5), log.extension (5), resolver (5), utils (5) -> felix-parent 9. - bundlerepository additionally declares junit and mockito explicitly, because felix-parent 2.1 supplied them to every module and felix-parent 9 does not. mockito-all 1.x is replaced by mockito-core, as its bundled cglib cannot generate classes on JDK 25. - resolver: mockito-all 1.10.19 -> mockito-core 5.18.0, for the same reason. Compiler levels below 8 are rejected outright by javac 25: - log, log.extension: maven.compiler.source/target 1.7 -> 8. - connect (1.6), bundlerepository (1.5) and the three examples/extenderbased.* modules (1.5) -> 8. configadmin: UpdateThread.terminate() called Thread.stop() when a worker failed to join within 5 seconds. Thread.stop() has thrown UnsupportedOperationException since Java 20, so that path could only turn a slow shutdown into a failed one. It now interrupts the worker instead. framework.tck: tck.bndrun still required assertj-core [3.27.3,3.27.4) after the dependency was bumped to 3.27.7, so the bndrun could not resolve and the TCK failed on every JDK, not just 25. CI: build bundlerepository, configadmin, connect, log.extension, resolver and utils, so the modules changed here are covered by the matrix. README: document that the framework no longer supports the OSGi security layer and which Java versions CI covers. Verified on JDK 25 (Corretto 25.0.2), each against a JDK 21 control: utils 108 tests, configadmin 111, bundlerepository 42, resolver 28, plus connect, log, log.extension and examples building clean. Not addressed here: iPOJO (a removal PR is open), eventadmin (not actively maintained; its integration tests fetch a bundle from the long-dead repository.springsource.com and its maven-bundle-plugin predates a bnd fix for a TreeMap.computeIfAbsent ConcurrentModificationException on Java 9+), and the wider bnd/maven-bundle-plugin upgrade, which only matters once a module is actually compiled at release 25. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
An audit of the workflow against the modules changed in this PR found three gaps: - gogo was listed for pull_request but not for push, so a push to master touching only gogo never triggered a build. - framework.tck appeared in neither trigger and had no path filter. The TCK step is gated on the framework filter, so a change to the TCK alone - such as the tck.bndrun assertj range fix in this PR - did not trigger a build at all. It is now part of the framework filter and both triggers. - examples is changed here but was not covered; it now has a filter and a build step. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Status update: SecurityManager removed, JDK 25 build blockers fixed, TCK green, framework at 8.0.0Picking up the two-branch approach discussed in #433 (comment) and confirmed by @mbien's JEP 14 reference: this branch drops the Security Manager entirely and fixes what actually breaks on JDK 25. The branch has also been synced with master. Everything below was verified locally on JDK 25.0.2, each time against a JDK 21 control run on the same tree, so that pre-existing and platform-specific failures were not misattributed to JDK 25. What actually blocked JDK 25Worth recording, because it was much narrower than expected:
Probing the API directly on JDK 25: only SecurityManager removal (−8,000 lines)Enforcement is gone; the OSGi API types are kept, so bundles referencing
Two pre-existing framework bugs found by the TCK — now split out into #554Once the TCK could actually launch (see below) it reported 33 errors, all from These are now proposed separately against 1. m_map = Collections.emptyMap();
if(map != null ) {
}
m_map.putAll(map);
2. See #554 for the details, the fix and the regression tests. A backport may be worth considering, since The TCK was failing on every JDK — three stacked causes
Plus the two Causes 1 and 3 break the TCK on every JDK, not just 25, so they are also part of #554 to make that PR verifiable against CI was reporting failing builds as green
Also fixed: Other JDK 25 blockers fixed
ScopeThis PR is deliberately limited to the modules that were already in the CI matrix and genuinely broke on JDK 25. Work that turned out to be unrelated has been split into its own pull requests, stacked on this branch:
Version bumpsVersions follow whether a module's code actually changed:
The framework's major bump rests on the removal of the OSGi security layer, a documented behaviour change, and therefore holds independently of the minimum Java version discussion. Even keeping the baseline at 8, that removal alone warrants it. Its manifest now reads: Note that
No exported package versions were changed. Everything the framework exports is Results on JDK 25
Deliberately not in scope
Separate:
|
Removes continue-on-error from the SCR, HTTP and Framework CI steps. It was reporting those steps as successful even when Maven failed, which hid a real JDK 25 failure in HTTP: the run for the previous commit showed "BUILD SUCCESS" for the step while the log contained BUILD FAILURE. What it was hiding, and the follow-on TCK failures: - http/jetty and http/jetty12 provision ASM 9.7 bundles into the pax-exam container. ASM 9.7 only understands class files up to Java 23, so on JDK 25 JakartaSpecificWebsocketIT failed inside Jetty's websocket component with "Unsupported class file major version 69". Provision ASM 9.8 instead. This surfaced now because the jetty module was re-enabled in this branch after having been commented out. - framework.tck: with the assertj range corrected the bndrun resolves, and the run then failed in the bnd launcher with "Setting a system-wide Policy object is not supported". aQute.launcher.Launcher.activate() called Policy.setPolicy() unguarded; bnd has since wrapped that in a catch specifically because Java 24 rejects it. The tester bundle was pinned to biz.aQute.junit 6.4.1, which predates the fix, while the bnd plugins were on 7.1.0. Move both to 7.4.0, matching the bndlib version the repository already uses elsewhere. - framework.tck declared junit-platform-launcher twice, which Maven warns is malformed and may reject in future. Removed the duplicate. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The "TODO remove this change" suffixes were added to the framework, http and scr project names purely to make the path filters fire while this branch was being developed. Those modules now have real changes, so the markers are no longer needed and the names are restored. Also drops BundleImpl.m_smEx and m_classloader. The AdaptPermission check that used the caller class context is gone, leaving both fields unread while the class initialiser still allocated a SecurityManagerEx. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… the TCK
With the assertj range corrected the bndrun resolves, but the TCK then failed to
start assertj-core:
Unable to resolve assertj-core [8](R 8.0): missing requirement
osgi.wiring.package; (&(osgi.wiring.package=net.bytebuddy)
(version>=1.18.0)(!(version>=2.0.0)))
assertj-core 3.27.7 imports net.bytebuddy [1.18.0,2.0.0), but the TCK still pinned
byte-buddy 1.17.5. The dependency was bumped from 3.27.3 to 3.27.7 in #478 without
byte-buddy following, which is the same omission that left the bndrun requiring
assertj [3.27.3,3.27.4). Move byte-buddy to 1.18.12 in both the pom and the bndrun.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a concurrency group so that pushing again to a branch cancels the run still in progress for the previous commit, instead of letting the whole JDK matrix finish for a revision nobody is waiting on. github.head_ref is only set for pull_request events, so pushes to master fall back to the unique run_id and are never cancelled. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…the TCK With the TCK finally able to launch, it reported 33 errors across BundleContextFilterTests and DivTests. Both come from FilterImpl.WrapperCapability, which was damaged by 466eb93 ("[fw] reduce warning related to types Classes"), a generics cleanup. These are pre-existing defects on master, not fallout from the SecurityManager removal. The Map constructor lost its assignment into a stray empty if block: m_map = Collections.emptyMap(); if(map != null ) { } m_map.putAll(map); so m_map was an immutable empty map and putAll threw UnsupportedOperationException for any non-empty map, or NPE for a null one. It previously read m_map = (map == null) ? Collections.EMPTY_MAP : map; which is restored. Filter.matches(Map) has therefore been broken on master since April 2025. The ServiceReference constructor was rewritten from reading properties one by one to new DictionaryToMap(sr.getProperties(), false). ServiceReference.getProperties() was only added in OSGi Core 1.10 and is not implemented by every ServiceReference; the TCK's own mock throws UnsupportedOperationException for it. Restored to getPropertyKeys()/getProperty(), which every implementation supports. The OSGi Core TCK now passes on JDK 25 (8.0.0 is the latest published version). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The pull_request trigger filters on the BASE branch, and only accepted master. A stacked pull request - one opened against another feature branch rather than master - therefore matched neither trigger and got no CI at all, which is what happened to PR #552. Accept feature/** and maintenance/** as a base as well, so stacked work is built before it is merged down. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…onments
Removing the security layer changes documented framework behaviour, and several
modules now declare a higher osgi.ee than before, which is resolution affecting: a
deployment that resolved against the old requirement will not resolve against the
new one.
The framework and the artifacts versioned in lockstep with it go to 8.0.0-SNAPSHOT.
Its manifest now reads:
Bundle-Version: 8.0.0.SNAPSHOT
Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=9))"
tck.bndrun pinned -runfw to [7.0.0,8), which would have excluded the new framework,
so that range moves to [8.0.0,9).
The remaining modules were compiling against an old released felix-parent resolved
from Central, because their declared parent version did not match the local pom.
Correcting that raised their compiler level as a side effect:
utils 1.11.9 -> 2.0.0 (EE 1.7 -> 1.8)
log 1.3.3 -> 2.0.0 (EE 1.7 -> 1.8)
log.extension 1.0.1 -> 2.0.0 (EE 1.7 -> 1.8)
configadmin 1.9.27 -> 2.0.0 (EE 1.7 -> 1.8)
connect 0.2.1 -> 1.0.0 (EE 1.6 -> 1.8)
resolver 2.1.0 -> 3.0.0 (EE 1.6 -> 1.8)
bundlerepository 2.0.11 -> 3.0.0 (EE 1.5 -> 1.8)
Adds framework/README.md describing what differs between the 7.x and 8.x lines and
what upgrading requires, plus a changelog entry.
The OSGi Core R8 TCK passes on JDK 25 with the framework at 8.0.0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reverts the version bumps applied to utils, log, log.extension, connect, resolver, bundlerepository and configadmin. Their build fixes stay; only the version changes are withdrawn. The reasoning for bumping them was that repairing their stale parent poms raised their osgi.ee, which is resolution affecting. That does not hold up: - Six of the seven have no code change at all. Their only modification is pom configuration, and the version recorded in a pom is the version of the next release, which is a release-manager decision taken against everything that lands before that release rather than something this change should pre-empt. - The old execution environments were themselves accidental. Those modules compiled at 1.5 to 1.7 only because a stale parent version meant Maven resolved an old felix-parent from Central instead of the local one, so moving to 1.8 restores the intended level rather than deliberately dropping support. - connect going from 0.2.1 to 1.0.0 was the least defensible: for a 0.x artifact a major bump conveys little, while 1.0.0 asserts API stability that nothing here establishes. - configadmin's one code change, Thread.stop() to interrupt(), is a bug fix. Thread.stop() has thrown UnsupportedOperationException since Java 20, so the previous code could only fail. The framework stays at 8.0.0-SNAPSHOT, together with framework.tck, main and main.distribution which are versioned in lockstep with it. That bump rests on the removal of the OSGi security layer, a documented behaviour change across 16 modified source files, and holds independently of the execution environment. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Splits out work that was not about Java 25 support, and aligns version changes with whether a module's code actually changed. Moved to separate pull requests: - utils, connect, bundlerepository, resolver, log.extension and the examples/extenderbased modules. Their builds fail on a modern JDK, but none of them has a code change, none was in the CI matrix, and each releases independently, so repairing their stale parent poms belongs in its own change. - configadmin's Thread.stop() to interrupt() fix. Thread.stop() has thrown since Java 20, so it is a bug fix in its own right rather than part of Java 25 support. Dropped entirely: log needed no change. Its maven.compiler.source/target of 1.7 is overridden by felix-parent's release configuration, and it already builds on JDK 25 unmodified. Verified. Version changes now follow whether code changed: - framework 7.1.0 -> 8.0.0. 16 source files changed and the OSGi security layer is removed. - scr 2.2.19 -> 2.3.0, webconsole 5.0.19 -> 5.1.0, gogo.runtime 1.1.7 -> 1.2.0. Each has code changes removing permission enforcement. No exported API differs and the removed code only ran with a SecurityManager installed, which cannot happen on the JDKs this line targets, so a minor bump rather than a major one. - framework.tck, main and main.distribution keep their versions. They have no code changes; only their reference to the framework moves to 8.0.0-SNAPSHOT, which the build requires. - http keeps its version. Only test code changed there. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Removing the split-out modules from the workflow left their filter keys behind
without their path lists:
configadmin:
utils:
resolver:
...
That is still valid YAML, as a nested mapping, which is why a parse check did not
catch it, but it is not what dorny/paths-filter expects.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Every module whose code changed now documents it where that module already keeps its history: - scr/changelog.txt gains a 2.3.0 entry. - gogo/runtime/doc/changelog.txt gains a 1.1.6 to 1.2.0 entry. - webconsole/README.md gains a 5.1.0 entry under Releases, which is where that project records its history rather than in a separate changelog file. framework/doc/changelog.txt was already updated with the 8.0.0 entry. http is not included: only its test code changed. The webconsole Security section is left alone. It documents HTTP authentication and CSRF, which is unrelated to the Java Security Manager. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ocal build
The TCK step failed to resolve the framework:
Could not find artifact org.apache.felix:org.apache.felix.framework:jar:
8.0.0-SNAPSHOT in apache.snapshots
The framework step ran clean verify, which does not install. The TCK is a separate
Maven invocation, so it resolves org.apache.felix.framework from the repository
rather than from the build that just ran. While the framework was 7.1.0-SNAPSHOT that
version existed in apache.snapshots, so resolution succeeded and the TCK quietly
exercised the published snapshot instead of the code under test. Renaming to 8.0.0
turned that silent substitution into a resolution error.
Running clean install makes the TCK test the framework this build produced, which is
what the step was always meant to do.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…sion
gogo.runtime moved to 1.2.0-SNAPSHOT, but gogo.jline still declared a dependency on
1.1.7-SNAPSHOT, which no longer exists locally and is not published:
Could not find artifact org.apache.felix:org.apache.felix.gogo.runtime:jar:
1.1.7-SNAPSHOT in apache.snapshots
gogo.jline is the only module referencing the snapshot; gogo.bom and gogo.command
reference the released 1.1.4 and are unaffected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
main is changed by this branch, its dependency on the framework moves to
8.0.0-SNAPSHOT, and it embeds the framework, yet nothing verified it: there was no
main path filter and no build step, so a break would only have surfaced at release
time.
Building it locally first showed it does not build on a modern JDK at all, for the
same reason as the modules in the split-out build repair change: it declares
felix-parent 6, which does not match the local pom, so Maven resolves the released
parent from Central. That parent brings in ianal-maven-plugin 1.0-alpha-1, which
reflects into java.io and is blocked from Java 16 on:
Unable to make private java.io.File(java.lang.String,java.io.File) accessible:
module java.base does not "opens java.io" to unnamed module
Moving to felix-parent 9 removes ianal but pins maven-antrun-plugin 3.1.0, which
rejects the legacy tasks element:
You are using 'tasks' which has been removed from the maven-antrun-plugin
Both antrun executions now use target instead.
main has no code change, so its version stays at 7.1.0-SNAPSHOT. The step runs after
the framework step, which installs the framework it embeds.
Verified on JDK 25: main builds, the jar embeds the framework, and launching it
starts the framework and keeps running, with no Security Manager error. The only
output is the known sun.misc.Unsafe warning from URLHandlers.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…uild output main bundles the framework, so shipping main 7.1.0 containing framework 8.0.0 would be misleading. Its version moves to 8.0.0-SNAPSHOT alongside the framework. main.distribution is unaffected: it pins framework.version to the released 7.0.5 and consumes published artifacts rather than this build. Also removes six jars totalling 1.7 MB that were committed by mistake in the previous commit. main/bundle is populated by the build, is not tracked on master, and was not ignored, so a plain add picked it up. main/bundle, main/bin and main/conf are now in .gitignore, all three being build output of this module. Verified on JDK 25: main builds at 8.0.0-SNAPSHOT and the build no longer leaves untracked artifacts behind. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
main.distribution copies bundles into its basedir the same way main does, so building it leaves untracked jars behind in bundle/ and bin/. Both are now ignored. main.distribution/conf is tracked and is deliberately not ignored. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Try-out building framework and HTTP subprojects against java 25 to see what will break