JDK 27 Upgrade Triggers Lombok Unsafe Warnings: Root Cause and Clean Workaround

After upgrading to JDK 27, Lombok 1.18.48 emits four Unsafe warnings during compilation because it uses sun.misc.Unsafe to bypass module access checks; the Lombok team won't fix it to preserve zero-config usability, but adding --sun-misc-unsafe-memory-access=allow to the forked compiler JVM silences the warnings.

Tech Musings
Tech Musings
Tech Musings
JDK 27 Upgrade Triggers Lombok Unsafe Warnings: Root Cause and Clean Workaround

Background

A Spring Boot 4.1.2 project upgraded to Java 27 (Corretto 27.0.1) with Lombok 1.18.48. Running mvn clean test passes all 45 tests, but the compilation phase emits four WARNING lines originating from Lombok.

Conclusion first: this is a side effect of Lombok using sun.misc.Unsafe to bypass Java module system access checks. The Lombok maintainers have explicitly stated they will not fix it for now. The cleanest workaround is to pass --sun-misc-unsafe-memory-access=allow to the build JVM. --add-opens does not suppress this warning.

1. Phenomenon

The compilation output shows:

WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
WARNING: sun.misc.Unsafe::objectFieldOffset has been called by lombok.permit.Permit
WARNING: Please consider reporting this to the maintainers of class lombok.permit.Permit
WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release

Key characteristics:

Only appears on JDK 24+ (older JDKs lack this warning mechanism).

Printed once per JVM process, naming objectFieldOffset. The JVM warns on first use of each Unsafe memory-access method; the first caller in Lombok is the static initializer of Permit calling UNSAFE.objectFieldOffset(f) to get the offset of AccessibleObject.override. Subsequent calls to setAccessible using UNSAFE.putBoolean(...) are silent because the warning has already been issued.

Occurs during the compile phase because Lombok runs as an annotation processor inside Maven's javac process.

Does not affect compilation artifacts or runtime: Lombok is a provided compile-time tool; Unsafe is only used during build.

2. Cause: Forging Reflection Permissions via Memory Offsets

Lombok is not a typical annotation processor—it rewrites the AST (e.g., @Getter directly modifies the syntax tree). To do so it must call internal javac/ECJ APIs ( com.sun.tools.javac.*, org.eclipse.jdt.internal.*). The chain:

Since JDK 9's module system, jdk.compiler does not open these packages. Calling setAccessible(true) on their members triggers InaccessibleObjectException unless the build JVM adds --add-opens.

Lombok avoids requiring users to add flags. setAccessible(true) merely sets the override field inside AccessibleObject to true (after which reflection skips access checks). Lombok bypasses setAccessible and writes this field directly.

Writing the field itself requires privilege, so Lombok uses Unsafe.objectFieldOffset (to get the field's memory offset) + Unsafe.putBoolean (to write raw memory).

Source snippet from lombok-1.18.48:

private static final sun.misc.Unsafe UNSAFE =
    (sun.misc.Unsafe) reflectiveStaticFieldAccess(sun.misc.Unsafe.class, "theUnsafe");

public static <T extends AccessibleObject> T setAccessible(T accessor) {
    if (INIT_ERROR == null) {
        UNSAFE.putBoolean(accessor, ACCESSIBLE_OVERRIDE_FIELD_OFFSET, true); // privileged write
    } else {
        accessor.setAccessible(true); // fallback when Unsafe unavailable
    }
    return accessor;
}

Two details:

Unsafe first, fallback later — as long as Unsafe is available, the first branch always runs. This is why --add-opens cannot eliminate the warning.

If the override field cannot be found, Lombok falls back to a fake class with the same memory layout to estimate the offset: class Fake { boolean override; Object accessCheckCache; }. The author comments "The below seems very risky".

The single warning occurs in the static block at objectFieldOffset (once per JVM). The daily work is in setAccessible 's putBoolean — Lombok calls setAccessible for every internal Method, Field, Constructor (dozens per compilation), each time writing override to true via the offset. These calls happen after the warning, so they are silent.

The warning itself stems from JDK's deprecation plan: JDK 23 (JEP 471) marked Unsafe memory-access methods for future removal; JDK 24 (JEP 498) started printing these four runtime warnings on first use by default.

3. Why the Official Team Won't Fix

The issue trail is clear: #3852 (closed 2025-03 by Lombok author rzwitserloot) → #3907 (dup) → #4046 (follow-up). Key quotes:

"We're aware, we won't forget." … "the solution isn't set in stone yet: having a ticket for it feels like more a burden than a useful thing" — rzwitserloot
"Lombok does not need sun.misc.Unsafe, we only use it to make it way easier to setup a lombok build… You have to add a bunch of --add-exports and --add-opens flags if we do this without additional changes." — Rawi01

Developers complain on two points:

Fixing isn't hard, but explaining the fix to users is: removing Unsafe would force every user's build script to add a string of --add-exports / --add-opens, breaking Lombok's 2009 core promise: "drop a jar on the classpath, nothing else needed."

The real problem is far larger than four warning lines: "how to keep parasitizing inside the compiler after the module system closes the back door." No solution is agreed upon; the maintainers consider keeping an unsolvable issue open a burden, so they closed it as "done" without a fix. Community pushback led to #4046 for tracking.

In short: this is a product decision, not technical laziness. Lombok could fix it, but the cost is losing its zero-config selling point. Unsafe is its zero-config foundation; the plan is to wait until JDK actually denies/removes Unsafe, then switch to "skip Unsafe on new JDK + require users to add flags themselves."

4. Where the Difficulty Lies

No legal alternative API. The module system provides no "self-open" hole: MethodHandle, privateLookupIn, VarHandle are all unopened. Unsafe is the only master key, and JDK is systematically reclaiming it — the only alternative is "push flags to users."

Flags must target the right JVM, and topologies vary wildly. javac runs inside Maven/Gradle daemons; flags must go through compiler plugin fork config or org.gradle.jvmargs to reach the correct JVM. Eclipse/IDE builds use their own JVMs. Making one solution work across all build scenarios incurs huge documentation and support cost.

External deadline remains undefined. JEP 498 says default deny in "JDK 26 or later", removal in two waves. As of JDK 27 (observed default still warn in 2026-10), there is no hard deadline, so waiting is rational.

5. Temporary Workarounds

Option A: --sun-misc-unsafe-memory-access=allow (Recommended)

Semantics: "allow use, no warning", zero behavior change. This is a JVM option , not a compiler option, so it cannot be placed directly in <compilerArgs>. The correct approach is to fork javac into a separate process and pass the option via -J prefix, configured in pom.xml:

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
    <fork>true</fork>
    <compilerArgs>
        <arg>-J--sun-misc-unsafe-memory-access=allow</arg>
    </compilerArgs>
    <!-- annotationProcessorPaths etc. unchanged -->
</configuration>
</plugin>

Tested (JDK 27 + 357 source files): fork=true + -J--sun-misc-unsafe-memory-access=allow: build succeeds, warnings drop to zero. Cost: one extra javac subprocess per compilation, negligible speed impact.

Without fork, raw <arg>--sun-misc-unsafe-memory-access=allow</arg>: build fails , javac reports "invalid flag" because <compilerArgs> feeds javac itself, which doesn't recognize this JVM option.

Gradle equivalent:

tasks.withType(JavaCompile).configureEach { options.forkOptions.jvmArgs << '--sun-misc-unsafe-memory-access=allow' }

Validity reminder: this flag is temporary; from JEP 498 Phase 3 (default deny) onward, allow may become unavailable.

Option B: --add-opens — Tested Ineffective, Don't Bother

Many articles suggest "add --add-opens jdk.compiler/... ". Tested (JDK 27 + Lombok 1.18.48): opening all 10 javac packages, the same four warnings still appear. Reason: Permit.setAccessible uses Unsafe first; setAccessible is only the fallback when Unsafe is unavailable. --add-opens doesn't touch the primary path. Its real use case is the future configuration Lombok will require when forced to switch; adding it now is harmless but useless.

Option C: Keep Tracking

Lombok versions track JDK (1.18.48 = JDK 27 support). Watch issue #4046. When a future JDK defaults to deny, the Unsafe path will throw exceptions — then Lombok will release a version requiring --add-opens; upgrade and add flags as instructed.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

MavenBuild ConfigurationAnnotation ProcessingLombokCompiler PluginUnsafeJDK 27Java Module System
Tech Musings
Written by

Tech Musings

Capturing thoughts and reflections while coding.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.