What
Paperclip starts the server from a URLClassLoader over the patched jars (Paperclip.main). JDK 25's AOT cache (JEP 483, JEP 514) only caches classes loaded by the JVM's built-in loaders. So a cache recorded through Paperclip holds none of Minecraft's or Paper's classes. The training run skipped all 12,520 of them:
[warning][aot] Skipping net/minecraft/world/level/block/TntBlock: Unsupported location
... (12,520 lines)
Started from the same patched jars on the app class path, the same cache cuts startup by about a third.
Numbers
Paper 26.2-124, Temurin 25.0.4+7, -Xms1G -Xmx1G, a fresh world each start (level-seed=1), on an 8-core Linux box. Time from java to Done (:
| Start |
Time to Done |
java -jar paper.jar (Paperclip), as today |
13.3s |
| app class path, no cache |
12.1s |
app class path, -XX:AOTCache=paper.aot |
7.9–8.7s |
The cache was recorded with -XX:AOTCacheOutput=paper.aot on one start that ran to Done and was then stopped. It's about 195MB.
How we got Paper onto the app class path
We ran -Dpaperclip.patchonly=true into a bundlerRepoDir. Next to it we put a manifest-only jar, Main-Class: org.bukkit.craftbukkit.Main, whose Class-Path lists versions/… and then libraries/…, in the order META-INF/versions.list and META-INF/libraries.list give. The paths are relative to the jar, so the folder can move. Then:
java -XX:AOTCache=paper.aot -jar <repo>/launch.jar --nogui
Our server images now start this way, with a plugin loaded, and pass our start-up checks. The class-path order is the same as Paperclip's, versions first, as setupClasspath requires.
Two things we learned that a user would need to know:
- The JVM checks every class-path jar's size and modification time against the cache. If anything differs, for example after a repatch, it prints
[error][aot] … timestamp has changed and starts without the cache. So a stale cache fails safe, just slower.
- A cache recorded with the JDK and the jars at other paths still works when they're moved, as long as their relative layout and times are the same. We record ours at image build time.
Proposal
An opt-in, leaving the default launch alone. For example, while patching Paperclip could also write launch.jar (as above) into the bundlerRepoDir, perhaps only when asked with a system property. Anyone wanting the AOT cache would then run that jar instead of paper.jar.
This keeps what #35 was after: the server loaded as if started with plain java -jar, on the JVM's own loader. It would also sidestep the service-loader problem in #66, since the app loader is the parent there.
If a Paperclip-side change isn't wanted, a short note in the readme on doing this by hand with paperclip.patchonly would still help people on Java 25.
I'm happy to open a PR for whichever shape you prefer.
What
Paperclip starts the server from a
URLClassLoaderover the patched jars (Paperclip.main). JDK 25's AOT cache (JEP 483, JEP 514) only caches classes loaded by the JVM's built-in loaders. So a cache recorded through Paperclip holds none of Minecraft's or Paper's classes. The training run skipped all 12,520 of them:Started from the same patched jars on the app class path, the same cache cuts startup by about a third.
Numbers
Paper 26.2-124, Temurin 25.0.4+7,
-Xms1G -Xmx1G, a fresh world each start (level-seed=1), on an 8-core Linux box. Time fromjavatoDone (:java -jar paper.jar(Paperclip), as today-XX:AOTCache=paper.aotThe cache was recorded with
-XX:AOTCacheOutput=paper.aoton one start that ran toDoneand was then stopped. It's about 195MB.How we got Paper onto the app class path
We ran
-Dpaperclip.patchonly=trueinto abundlerRepoDir. Next to it we put a manifest-only jar,Main-Class: org.bukkit.craftbukkit.Main, whoseClass-Pathlistsversions/…and thenlibraries/…, in the orderMETA-INF/versions.listandMETA-INF/libraries.listgive. The paths are relative to the jar, so the folder can move. Then:Our server images now start this way, with a plugin loaded, and pass our start-up checks. The class-path order is the same as Paperclip's, versions first, as
setupClasspathrequires.Two things we learned that a user would need to know:
[error][aot] … timestamp has changedand starts without the cache. So a stale cache fails safe, just slower.Proposal
An opt-in, leaving the default launch alone. For example, while patching Paperclip could also write
launch.jar(as above) into thebundlerRepoDir, perhaps only when asked with a system property. Anyone wanting the AOT cache would then run that jar instead ofpaper.jar.This keeps what #35 was after: the server loaded as if started with plain
java -jar, on the JVM's own loader. It would also sidestep the service-loader problem in #66, since the app loader is the parent there.If a Paperclip-side change isn't wanted, a short note in the readme on doing this by hand with
paperclip.patchonlywould still help people on Java 25.I'm happy to open a PR for whichever shape you prefer.