diff --git a/.github/workflows/ant.yml b/.github/workflows/ant.yml
index 0c5cc94d..de5c352f 100644
--- a/.github/workflows/ant.yml
+++ b/.github/workflows/ant.yml
@@ -150,10 +150,10 @@ jobs:
needs: build
strategy:
matrix:
- jdk: [8, 11, 17, 21, 23]
+ jdk: [8, 11, 17, 21, 25]
tool:
- {name: "maven", cmd: "mvn compile"}
- - {name: "gradle", cmd: "gradle assemble"}
+ - {name: "gradle", cmd: "gradle assemble", buildArgs: {"25": "--build-arg gradle=9.1.0"}}
- {name: "ant", cmd: "ant dist"}
- {name: "bazel", cmd: "bazel build //:ProjectRunner"}
fail-fast: false
@@ -170,7 +170,7 @@ jobs:
- name: Build container
working-directory: ./docker
- run: docker build --build-arg jdk=${{ matrix.jdk }} -t $IMAGE_NAME -f ${{ matrix.tool.name }}/Dockerfile .
+ run: docker build --build-arg jdk=${{ matrix.jdk }} ${{ matrix.tool.buildArgs[matrix.jdk] }} -t $IMAGE_NAME -f ${{ matrix.tool.name }}/Dockerfile .
- name: Compile in container
run: docker run --entrypoint="" -v $(pwd)/lombok.jar:/workspace/lombok.jar $IMAGE_NAME /bin/bash -c "cd classpath; ${{ matrix.tool.cmd }}"
@@ -180,7 +180,7 @@ jobs:
needs: build
strategy:
matrix:
- jdk: [8, 11, 17, 21, 23]
+ jdk: [8, 11, 17, 21, 25]
dir: [compileTests]
fail-fast: false
diff --git a/doc/changelog.markdown b/doc/changelog.markdown
index 3428c16e..cf6cceda 100644
--- a/doc/changelog.markdown
+++ b/doc/changelog.markdown
@@ -2,7 +2,8 @@ Lombok Changelog
----------------
### v1.18.43 "Edgy Guinea Pig"
-* No changes since v1.18.42 yet.
+* BUGFIX: On JDK25, `val` and `@ExtensionMethod` could sometimes cause erroneous errors (in that you see errors but compilation succeeds anyway) using javac. [#3947](https://github.com/projectlombok/lombok/issues/3947).
+* BUGFIX: `@Jacksonized` + fields marked `transient` would result in those transient fields being serialised which is surprising (and thus undesired) behaviour. [#3936](https://github.com/projectlombok/lombok/issues/3936).
### v1.18.42 (September 18th, 2025)
* FEATURE: All the various `@Log` annotations now allow you to change their access level (they still default to `private`). [#2280](https://github.com/projectlombok/lombok/issues/2280). Thanks to new contributor Liam Pace!
diff --git a/docker/ant/files/jdk-23/classpath/build.xml b/docker/ant/files/jdk-25/classpath/build.xml
similarity index 100%
rename from docker/ant/files/jdk-23/classpath/build.xml
rename to docker/ant/files/jdk-25/classpath/build.xml
diff --git a/docker/ant/files/jdk-23/modules/build.xml b/docker/ant/files/jdk-25/modules/build.xml
similarity index 100%
rename from docker/ant/files/jdk-23/modules/build.xml
rename to docker/ant/files/jdk-25/modules/build.xml
diff --git a/docker/maven/files/jdk-23/classpath/pom.xml b/docker/maven/files/jdk-25/classpath/pom.xml
similarity index 98%
rename from docker/maven/files/jdk-23/classpath/pom.xml
rename to docker/maven/files/jdk-25/classpath/pom.xml
index 52df5b57..b96de94f 100644
--- a/docker/maven/files/jdk-23/classpath/pom.xml
+++ b/docker/maven/files/jdk-25/classpath/pom.xml
@@ -7,7 +7,7 @@
UTF-8
- 23
+ 25
diff --git a/docker/maven/files/jdk-23/modules/pom.xml b/docker/maven/files/jdk-25/modules/pom.xml
similarity index 98%
rename from docker/maven/files/jdk-23/modules/pom.xml
rename to docker/maven/files/jdk-25/modules/pom.xml
index 8c673a42..f822c186 100644
--- a/docker/maven/files/jdk-23/modules/pom.xml
+++ b/docker/maven/files/jdk-25/modules/pom.xml
@@ -7,7 +7,7 @@
UTF-8
- 23
+ 25
diff --git a/docker/provision/jdk/java-23.sh b/docker/provision/jdk/java-23.sh
deleted file mode 100755
index 5c489e28..00000000
--- a/docker/provision/jdk/java-23.sh
+++ /dev/null
@@ -1,4 +0,0 @@
-apt-get update && apt-get install -y wget
-wget https://download.java.net/java/GA/jdk23/3c5b90190c68498b986a97f276efd28a/37/GPL/openjdk-23_linux-x64_bin.tar.gz -O jdk.tar.gz
-tar -xzf jdk.tar.gz -C /opt/
-mv /opt/jdk-23 /opt/jdk
diff --git a/docker/provision/jdk/java-25.sh b/docker/provision/jdk/java-25.sh
new file mode 100755
index 00000000..a7d923d2
--- /dev/null
+++ b/docker/provision/jdk/java-25.sh
@@ -0,0 +1,4 @@
+apt-get update && apt-get install -y wget
+wget https://github.com/adoptium/temurin25-binaries/releases/download/jdk-25%2B36/OpenJDK25U-jdk_x64_linux_hotspot_25_36.tar.gz -O jdk.tar.gz
+tar -xzf jdk.tar.gz -C /opt/
+mv /opt/jdk-25+36 /opt/jdk
\ No newline at end of file
diff --git a/src/core/lombok/core/configuration/ConfigurationFile.java b/src/core/lombok/core/configuration/ConfigurationFile.java
index 8e4e687c..a510368c 100644
--- a/src/core/lombok/core/configuration/ConfigurationFile.java
+++ b/src/core/lombok/core/configuration/ConfigurationFile.java
@@ -97,7 +97,7 @@ public abstract class ConfigurationFile {
return file.exists() && file.isFile();
}
- private static String read(InputStream is) throws IOException {
+ static String read(InputStream is) throws IOException {
byte[] b = buffers.get();
ByteArrayOutputStream out = new ByteArrayOutputStream();
while (true) {
@@ -111,7 +111,7 @@ public abstract class ConfigurationFile {
private static class RegularConfigurationFile extends ConfigurationFile {
private final File file;
private ConfigurationFile parent;
-
+
private RegularConfigurationFile(File file) {
super(file.getPath());
this.file = file;
@@ -172,7 +172,7 @@ public abstract class ConfigurationFile {
is.close();
}
}
-
+
@Override ConfigurationFile parent() {
if (parent == null) {
File parentFile = file.getParentFile().getParentFile();
@@ -322,15 +322,15 @@ public abstract class ConfigurationFile {
@Override CharSequence contents() throws IOException {
return contents;
}
-
+
@Override boolean exists() {
return true;
}
-
+
@Override public ConfigurationFile resolve(String path) {
return null;
}
-
+
@Override ConfigurationFile parent() {
return null;
}
diff --git a/src/core/lombok/eclipse/handlers/HandleJacksonized.java b/src/core/lombok/eclipse/handlers/HandleJacksonized.java
index ab43f0a7..316c6c88 100644
--- a/src/core/lombok/eclipse/handlers/HandleJacksonized.java
+++ b/src/core/lombok/eclipse/handlers/HandleJacksonized.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020-2025 The Project Lombok Authors.
+ * Copyright (C) 2020-2026 The Project Lombok Authors.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -43,6 +43,7 @@ import lombok.Builder;
import lombok.ConfigurationKeys;
import lombok.core.AnnotationValues;
import lombok.core.HandlerPriority;
+import lombok.core.JacksonAnnotationType;
import lombok.core.AST.Kind;
import lombok.core.configuration.JacksonVersion;
import lombok.core.handlers.HandlerUtil;
@@ -62,12 +63,9 @@ import lombok.spi.Provides;
@Provides
@HandlerPriority(-512) // Above Handle(Super)Builder's level (builders must be already generated).
public class HandleJacksonized extends EclipseAnnotationHandler {
-
- private static final char[][] JACKSON3_JSON_POJO_BUILDER_ANNOTATION = Eclipse.fromQualifiedName("tools.jackson.databind.annotation.JsonPOJOBuilder");
- private static final char[][] JACKSON2_JSON_POJO_BUILDER_ANNOTATION = Eclipse.fromQualifiedName("com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder");
- private static final char[][] JACKSON3_JSON_DESERIALIZE_ANNOTATION = Eclipse.fromQualifiedName("tools.jackson.databind.annotation.JsonDeserialize");
- private static final char[][] JACKSON2_JSON_DESERIALIZE_ANNOTATION = Eclipse.fromQualifiedName("com.fasterxml.jackson.databind.annotation.JsonDeserialize");
- private static final char[][] JSON_PROPERTY_ANNOTATION = Eclipse.fromQualifiedName("com.fasterxml.jackson.annotation.JsonProperty");
+ static boolean hasAnnotation(EclipseNode node, JacksonAnnotationType annotation) {
+ return EclipseHandlerUtil.hasAnnotation(annotation.getQualifiedName(), node);
+ }
@Override public void handle(AnnotationValues annotation, Annotation ast, EclipseNode annotationNode) {
handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.JACKSONIZED_FLAG_USAGE, "@Jacksonized");
@@ -135,11 +133,7 @@ public class HandleJacksonized extends EclipseAnnotationHandler {
}
// Insert @JsonDeserialize on annotated class.
- if (hasAnnotation("com.fasterxml.jackson.databind.annotation.JsonDeserialize", tdNode)) {
- annotationNode.addError("@JsonDeserialize already exists on class. Either delete @JsonDeserialize, or remove @Jacksonized and manually configure Jackson.");
- return;
- }
- if (hasAnnotation("tools.jackson.databind.annotation.JsonDeserialize", tdNode)) {
+ if (hasAnnotation(tdNode, JacksonAnnotationType.JSON_DESERIALIZE2) || hasAnnotation(tdNode, JacksonAnnotationType.JSON_DESERIALIZE3)) {
annotationNode.addError("@JsonDeserialize already exists on class. Either delete @JsonDeserialize, or remove @Jacksonized and manually configure Jackson.");
return;
}
@@ -147,19 +141,22 @@ public class HandleJacksonized extends EclipseAnnotationHandler {
TypeReference builderClassExpression = namePlusTypeParamsToTypeReference(builderClassNode, null, p);
ClassLiteralAccess builderClassLiteralAccess = new ClassLiteralAccess(td.sourceEnd, builderClassExpression);
MemberValuePair builderMvp = new MemberValuePair("builder".toCharArray(), td.sourceStart, td.sourceEnd, builderClassLiteralAccess);
-
- JacksonVersion jacksonVersion = annotationNode.getAst().readConfigurationOr(ConfigurationKeys.JACKSONIZED_JACKSON_VERSION, JacksonVersion.getDefault());
- if (jacksonVersion == null || !jacksonVersion.isValid()) {
- annotationNode.addError("No valid jackson version selected.");
- return;
+
+ List jacksonVersions = annotationNode.getAst().readConfigurationOr(ConfigurationKeys.JACKSONIZED_JACKSON_VERSION, Arrays.asList());
+
+ if (jacksonVersions.isEmpty()) {
+ annotationNode.addWarning("Ambiguous: Jackson2 and Jackson3 exist; define which variant(s) you want in 'lombok.config'. See https://projectlombok.org/features/experimental/Jacksonized");
+ jacksonVersions = Arrays.asList(JacksonVersion.TWO);
}
-
- if (jacksonVersion.useJackson2()) {
- td.annotations = addAnnotation(td, td.annotations, JACKSON2_JSON_DESERIALIZE_ANNOTATION, builderMvp);
+
+ if (jacksonVersions.contains(JacksonVersion.TWO)) {
+ td.annotations = addAnnotation(td, td.annotations, JacksonAnnotationType.JSON_DESERIALIZE2.getQualifiednameAsCharArrayArray(), builderMvp);
}
- if (jacksonVersion.useJackson3()) {
- td.annotations = addAnnotation(td, td.annotations, JACKSON3_JSON_DESERIALIZE_ANNOTATION, builderMvp);
+
+ if (jacksonVersions.contains(JacksonVersion.THREE)) {
+ td.annotations = addAnnotation(td, td.annotations, JacksonAnnotationType.JSON_DESERIALIZE3.getQualifiednameAsCharArrayArray(), builderMvp);
}
+
// Copy annotations from the class to the builder class.
Annotation[] copyableAnnotations = findJacksonAnnotationsOnClass(td, tdNode);
builderClass.annotations = copyAnnotations(builderClass, builderClass.annotations, copyableAnnotations);
@@ -169,15 +166,16 @@ public class HandleJacksonized extends EclipseAnnotationHandler {
MemberValuePair withPrefixMvp = new MemberValuePair("withPrefix".toCharArray(), builderClass.sourceStart, builderClass.sourceEnd, withPrefixLiteral);
StringLiteral buildMethodNameLiteral = new StringLiteral(buildMethodName.toCharArray(), builderClass.sourceStart, builderClass.sourceEnd, 0);
MemberValuePair buildMethodNameMvp = new MemberValuePair("buildMethodName".toCharArray(), builderClass.sourceStart, builderClass.sourceEnd, buildMethodNameLiteral);
- if (jacksonVersion.useJackson2()) {
- builderClass.annotations = addAnnotation(builderClass, builderClass.annotations, JACKSON2_JSON_POJO_BUILDER_ANNOTATION, withPrefixMvp, buildMethodNameMvp);
+
+ if (jacksonVersions.contains(JacksonVersion.TWO)) {
+ builderClass.annotations = addAnnotation(builderClass, builderClass.annotations, JacksonAnnotationType.JSON_POJO_BUILDER2.getQualifiednameAsCharArrayArray(), withPrefixMvp, buildMethodNameMvp);
}
- if (jacksonVersion.useJackson3()) {
- builderClass.annotations = addAnnotation(builderClass, builderClass.annotations, JACKSON3_JSON_POJO_BUILDER_ANNOTATION, withPrefixMvp, buildMethodNameMvp);
+ if (jacksonVersions.contains(JacksonVersion.THREE)) {
+ builderClass.annotations = addAnnotation(builderClass, builderClass.annotations, JacksonAnnotationType.JSON_POJO_BUILDER3.getQualifiednameAsCharArrayArray(), withPrefixMvp, buildMethodNameMvp);
}
+
// @SuperBuilder? Make it package-private!
- if (superBuilderAnnotationNode != null)
- builderClass.modifiers = builderClass.modifiers & ~ClassFileConstants.AccPrivate;
+ if (superBuilderAnnotationNode != null) builderClass.modifiers = builderClass.modifiers & ~ClassFileConstants.AccPrivate;
}
private void handleJacksonizedAccessors(Annotation ast, EclipseNode annotationNode, EclipseNode annotatedNode, EclipseNode tdNode, TypeDeclaration td, EclipseNode accessorsAnnotationNode, boolean jacksonizedBuilder) {
@@ -196,19 +194,33 @@ public class HandleJacksonized extends EclipseAnnotationHandler {
// Add @JsonProperty to all fields. It will be automatically copied to the getter/setters later.
for (EclipseNode eclipseNode : tdNode.down()) {
if (eclipseNode.getKind() == Kind.FIELD) {
- createJsonPropertyForField(eclipseNode, annotationNode);
+ if (hasAnnotation(eclipseNode, JacksonAnnotationType.JSON_PROPERTY2) ||
+ hasAnnotation(eclipseNode, JacksonAnnotationType.JSON_IGNORE2)) {
+ return;
+ } else if (eclipseNode.isTransient()) {
+ createJsonIgnoreForField(eclipseNode, annotationNode);
+ } else {
+ createJsonPropertyForField(eclipseNode, annotationNode);
+ }
}
}
tdNode.rebuild();
}
private void createJsonPropertyForField(EclipseNode fieldNode, EclipseNode annotationNode) {
- if (hasAnnotation("com.fasterxml.jackson.annotation.JsonProperty", fieldNode)) return;
ASTNode astNode = fieldNode.get();
if (astNode instanceof FieldDeclaration) {
- FieldDeclaration fd = (FieldDeclaration)astNode;
+ FieldDeclaration fd = (FieldDeclaration) astNode;
StringLiteral fieldName = new StringLiteral(fd.name, 0, 0, 0);
- ((FieldDeclaration) astNode).annotations = addAnnotation(fieldNode.get(), fd.annotations, JSON_PROPERTY_ANNOTATION, fieldName);
+ ((FieldDeclaration) astNode).annotations = addAnnotation(fieldNode.get(), fd.annotations, JacksonAnnotationType.JSON_PROPERTY2.getQualifiednameAsCharArrayArray(), fieldName);
+ }
+ }
+
+ private void createJsonIgnoreForField(EclipseNode fieldNode, EclipseNode annotationNode) {
+ ASTNode astNode = fieldNode.get();
+ if (astNode instanceof FieldDeclaration) {
+ FieldDeclaration fd = (FieldDeclaration) astNode;
+ ((FieldDeclaration) astNode).annotations = addAnnotation(fieldNode.get(), fd.annotations, JacksonAnnotationType.JSON_IGNORE2.getQualifiednameAsCharArrayArray());
}
}
diff --git a/src/core/lombok/javac/CompilerMessageSuppressor.java b/src/core/lombok/javac/CompilerMessageSuppressor.java
index 7d7b81e6..fc820b90 100644
--- a/src/core/lombok/javac/CompilerMessageSuppressor.java
+++ b/src/core/lombok/javac/CompilerMessageSuppressor.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2011-2021 The Project Lombok Authors.
+ * Copyright (C) 2011-2025 The Project Lombok Authors.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -24,7 +24,10 @@ package lombok.javac;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
+import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.Collection;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
@@ -36,7 +39,6 @@ import javax.tools.JavaFileObject;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.JCDiagnostic;
-import com.sun.tools.javac.util.ListBuffer;
import com.sun.tools.javac.util.Log;
import lombok.permit.Permit;
@@ -53,9 +55,14 @@ public final class CompilerMessageSuppressor {
private static final Field deferDiagnosticsField, deferredDiagnosticsField, diagnosticHandlerField;
private static final ConcurrentMap, Field> handlerDeferredFields = new ConcurrentHashMap, Field>();
private static final Field NULL_FIELD;
+ private static final Class> DIAGNOSTIC_HANDLER;
+ private static final Class> DISCARD_DIAGNOSTIC_HANDLER;
+ private static final Method POP_DIAGNOSTIC_HANDLER;
+ private static final Constructor> DISCARD_DIAGNOSTIC_HANDLER_CONSTRUCTOR;
private Boolean dumpOnError, promptOnError;
private DiagnosticListener> contextDiagnosticListener, logDiagnosticListener;
private final Context context;
+ private Object diagnosticHandler;
private static final ThreadLocal> queueCache = new ThreadLocal>();
@@ -87,6 +94,11 @@ public final class CompilerMessageSuppressor {
diagnosticHandlerField = getDeclaredField(Log.class, "diagnosticHandler");
NULL_FIELD = getDeclaredField(JavacResolution.class, "NULL_FIELD");
+
+ DIAGNOSTIC_HANDLER = getClass("com.sun.tools.javac.util.Log$DiagnosticHandler");
+ DISCARD_DIAGNOSTIC_HANDLER = getClass("com.sun.tools.javac.util.Log$DiscardDiagnosticHandler");
+ POP_DIAGNOSTIC_HANDLER = DIAGNOSTIC_HANDLER != null ? Permit.permissiveGetMethod(Log.class, "popDiagnosticHandler", DIAGNOSTIC_HANDLER) : null;
+ DISCARD_DIAGNOSTIC_HANDLER_CONSTRUCTOR = DISCARD_DIAGNOSTIC_HANDLER != null ? Permit.permissiveGetConstructor(DISCARD_DIAGNOSTIC_HANDLER, Log.class) : null;
}
static Field getDeclaredField(Class> c, String fieldName) {
@@ -97,6 +109,14 @@ public final class CompilerMessageSuppressor {
}
}
+ static Class> getClass(String name) {
+ try {
+ return Class.forName(name);
+ } catch (Throwable t) {
+ return null;
+ }
+ }
+
public CompilerMessageSuppressor(Context context) {
this.log = Log.instance(context);
this.context = context;
@@ -118,16 +138,6 @@ public final class CompilerMessageSuppressor {
}
} catch (Exception e) {}
- if (diagnosticHandlerField != null) try {
- Object handler = diagnosticHandlerField.get(log);
- Field field = getDeferredField(handler);
- if (field != null) {
- queueCache.set((Queue>) field.get(handler));
- Queue> empty = new LinkedList