From cdfb0f551e4c856b7dac29ce5aea6dddbe711174 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Sun, 22 Feb 2026 18:51:31 +0100 Subject: [PATCH 1/9] [feature][`@CheckReturnValue`] Add @lombok.CheckReturnValue to generated methods where the return value should not be ignored. Adds the annotation to @With, @WithBy, @Builder.build(), and @SuperBuilder.build() methods. Controlled via lombok.addCheckReturnValueAnnotation config key (default: true). Static analysis tools (Error Prone, IntelliJ, SpotBugs) recognize @CheckReturnValue by simple name and will warn when the return value is discarded. --- src/core/lombok/CheckReturnValue.java | 44 ++++++++++++++ src/core/lombok/ConfigurationKeys.java | 10 +++- .../eclipse/handlers/EclipseHandlerUtil.java | 6 ++ .../eclipse/handlers/HandleBuilder.java | 3 +- .../eclipse/handlers/HandleSuperBuilder.java | 1 + .../lombok/eclipse/handlers/HandleWith.java | 3 +- .../lombok/eclipse/handlers/HandleWithBy.java | 3 +- .../lombok/javac/handlers/HandleBuilder.java | 3 +- .../javac/handlers/HandleSuperBuilder.java | 3 +- .../lombok/javac/handlers/HandleWith.java | 5 +- .../lombok/javac/handlers/HandleWithBy.java | 1 + .../javac/handlers/JavacHandlerUtil.java | 5 ++ .../CheckReturnValueBuilder.java | 59 +++++++++++++++++++ .../after-delombok/CheckReturnValueOff.java | 14 +++++ .../after-delombok/CheckReturnValueWith.java | 26 ++++++++ .../after-ecj/CheckReturnValueBuilder.java | 39 ++++++++++++ .../after-ecj/CheckReturnValueOff.java | 14 +++++ .../after-ecj/CheckReturnValueWith.java | 22 +++++++ .../before/CheckReturnValueBuilder.java | 5 ++ .../resource/before/CheckReturnValueOff.java | 9 +++ .../resource/before/CheckReturnValueWith.java | 10 ++++ 21 files changed, 277 insertions(+), 8 deletions(-) create mode 100644 src/core/lombok/CheckReturnValue.java create mode 100644 test/transform/resource/after-delombok/CheckReturnValueBuilder.java create mode 100644 test/transform/resource/after-delombok/CheckReturnValueOff.java create mode 100644 test/transform/resource/after-delombok/CheckReturnValueWith.java create mode 100644 test/transform/resource/after-ecj/CheckReturnValueBuilder.java create mode 100644 test/transform/resource/after-ecj/CheckReturnValueOff.java create mode 100644 test/transform/resource/after-ecj/CheckReturnValueWith.java create mode 100644 test/transform/resource/before/CheckReturnValueBuilder.java create mode 100644 test/transform/resource/before/CheckReturnValueOff.java create mode 100644 test/transform/resource/before/CheckReturnValueWith.java diff --git a/src/core/lombok/CheckReturnValue.java b/src/core/lombok/CheckReturnValue.java new file mode 100644 index 00000000..ef8f5c61 --- /dev/null +++ b/src/core/lombok/CheckReturnValue.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 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 + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Lombok adds this annotation to generated methods where the return value should not be ignored. + *

+ * For example, {@code @With} methods return a new instance, so ignoring the return value is always a bug. + * Similarly, {@code @Builder}'s {@code build()} method produces the built object. + *

+ * Static analysis tools (Error Prone, IntelliJ, SpotBugs) recognize {@code @CheckReturnValue} + * by simple class name, regardless of package, and will warn when the return value is discarded. + *

+ * If you want to opt out, you can add {@code lombok.addCheckReturnValueAnnotation = false} to + * {@code lombok.config}. + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.CLASS) +public @interface CheckReturnValue { +} diff --git a/src/core/lombok/ConfigurationKeys.java b/src/core/lombok/ConfigurationKeys.java index 997c550b..6c5c08f5 100644 --- a/src/core/lombok/ConfigurationKeys.java +++ b/src/core/lombok/ConfigurationKeys.java @@ -89,7 +89,15 @@ public class ConfigurationKeys { * If {@code true}, lombok generates {@code @lombok.Generated} on all fields, methods, and types that are generated. */ public static final ConfigurationKey ADD_LOMBOK_GENERATED_ANNOTATIONS = new ConfigurationKey("lombok.addLombokGeneratedAnnotation", "Generate @lombok.Generated on all generated code (default: true).") {}; - + + /** + * lombok configuration: {@code lombok.addCheckReturnValueAnnotation} = {@code true} | {@code false}. + * + * If {@code true}, lombok generates {@code @lombok.CheckReturnValue} on generated methods where the return value should not be ignored, + * such as {@code @With} methods and {@code @Builder}'s {@code build()} method. + */ + public static final ConfigurationKey ADD_CHECK_RETURN_VALUE_ANNOTATIONS = new ConfigurationKey("lombok.addCheckReturnValueAnnotation", "Generate @lombok.CheckReturnValue on generated methods where the return value should not be ignored (default: true).") {}; + /** * lombok configuration: {@code lombok.extern.findbugs.addSuppressFBWarnings} = {@code true} | {@code false}. * diff --git a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java index 7eaaf7b6..a479356d 100644 --- a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java +++ b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java @@ -2096,6 +2096,7 @@ public class EclipseHandlerUtil { private static final char[][] JAKARTA_ANNOTATION_GENERATED = Eclipse.fromQualifiedName("jakarta.annotation.Generated"); private static final char[][] LOMBOK_GENERATED = Eclipse.fromQualifiedName("lombok.Generated"); private static final char[][] EDU_UMD_CS_FINDBUGS_ANNOTATIONS_SUPPRESSFBWARNINGS = Eclipse.fromQualifiedName("edu.umd.cs.findbugs.annotations.SuppressFBWarnings"); + private static final char[][] LOMBOK_CHECK_RETURN_VALUE = Eclipse.fromQualifiedName("lombok.CheckReturnValue"); public static Annotation[] addSuppressWarningsAll(EclipseNode node, ASTNode source, Annotation[] originalAnnotationArray) { Annotation[] anns = originalAnnotationArray; @@ -2126,6 +2127,11 @@ public class EclipseHandlerUtil { return result; } + public static Annotation[] addCheckReturnValue(EclipseNode node, ASTNode source, Annotation[] originalAnnotationArray) { + if (Boolean.FALSE.equals(node.getAst().readConfiguration(ConfigurationKeys.ADD_CHECK_RETURN_VALUE_ANNOTATIONS))) return originalAnnotationArray; + return addAnnotation(source, originalAnnotationArray, LOMBOK_CHECK_RETURN_VALUE); + } + static Annotation[] addAnnotation(ASTNode source, Annotation[] originalAnnotationArray, char[][] annotationTypeFqn) { return addAnnotation(source, originalAnnotationArray, annotationTypeFqn, (ASTNode[]) null); } diff --git a/src/core/lombok/eclipse/handlers/HandleBuilder.java b/src/core/lombok/eclipse/handlers/HandleBuilder.java index c39acdc7..d1e31617 100755 --- a/src/core/lombok/eclipse/handlers/HandleBuilder.java +++ b/src/core/lombok/eclipse/handlers/HandleBuilder.java @@ -889,11 +889,12 @@ public class HandleBuilder extends EclipseAnnotationHandler { out.annotations = new Annotation[] {generateNamedAnnotation(job.source, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE)}; } out.receiver = generateBuildReceiver(job); + out.annotations = addCheckReturnValue(job.builderType, job.source, out.annotations); if (staticName == null) createRelevantNonNullAnnotation(job.builderType, out); out.traverse(new SetGeneratedByVisitor(job.source), (ClassScope) null); return out; } - + private TypeReference[] typeParameterNames(TypeParameter[] typeParameters) { if (typeParameters == null) return null; diff --git a/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java b/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java index 8ed1fa5a..8ced7e07 100644 --- a/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java +++ b/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java @@ -893,6 +893,7 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler { statements.add(new ReturnStatement(allocationStatement, 0, 0)); out.statements = statements.isEmpty() ? null : statements.toArray(new Statement[0]); out.receiver = HandleBuilder.generateBuildReceiver(job); + out.annotations = addCheckReturnValue(job.builderType, job.source, out.annotations); createRelevantNonNullAnnotation(job.builderType, out); out.traverse(new SetGeneratedByVisitor(job.source), (ClassScope) null); return out; diff --git a/src/core/lombok/eclipse/handlers/HandleWith.java b/src/core/lombok/eclipse/handlers/HandleWith.java index 153f0c4a..b286e769 100644 --- a/src/core/lombok/eclipse/handlers/HandleWith.java +++ b/src/core/lombok/eclipse/handlers/HandleWith.java @@ -292,7 +292,8 @@ public class HandleWith extends EclipseAnnotationHandler { param.annotations = copyAnnotations(source, copyableAnnotations, onParam.toArray(new Annotation[0])); EclipseHandlerUtil.createRelevantNonNullAnnotation(fieldNode, method); - + method.annotations = EclipseHandlerUtil.addCheckReturnValue(fieldNode, source, method.annotations); + method.traverse(new SetGeneratedByVisitor(source), parent.scope); copyJavadoc(fieldNode, method, CopyJavadoc.WITH); return method; diff --git a/src/core/lombok/eclipse/handlers/HandleWithBy.java b/src/core/lombok/eclipse/handlers/HandleWithBy.java index bfba91d4..e25bb843 100644 --- a/src/core/lombok/eclipse/handlers/HandleWithBy.java +++ b/src/core/lombok/eclipse/handlers/HandleWithBy.java @@ -375,7 +375,8 @@ public class HandleWithBy extends EclipseAnnotationHandler { createRelevantNonNullAnnotation(sourceNode, param, method); createRelevantNonNullAnnotation(fieldNode, method); - + method.annotations = addCheckReturnValue(fieldNode, source, method.annotations); + method.traverse(new SetGeneratedByVisitor(source), parent.scope); copyJavadoc(fieldNode, method, CopyJavadoc.WITH_BY); return method; diff --git a/src/core/lombok/javac/handlers/HandleBuilder.java b/src/core/lombok/javac/handlers/HandleBuilder.java index 1e023f20..ea3392e2 100644 --- a/src/core/lombok/javac/handlers/HandleBuilder.java +++ b/src/core/lombok/javac/handlers/HandleBuilder.java @@ -801,10 +801,11 @@ public class HandleBuilder extends JavacAnnotationHandler { } else { methodDef = maker.MethodDef(maker.Modifiers(toJavacModifier(job.accessInners), annsOnMethod), job.toName(job.buildMethodName), returnTypeCopy, List.nil(), List.nil(), thrownExceptions, body, null); } + addCheckReturnValue(methodDef.mods, job.builderType, job.sourceNode); if (staticName == null) createRelevantNonNullAnnotation(job.builderType, methodDef); return methodDef; } - + public static JCMethodDecl generateDefaultProvider(Name methodName, JavacNode fieldNode, List params, BuilderJob job) { JavacTreeMaker maker = fieldNode.getTreeMaker(); JCVariableDecl field = (JCVariableDecl) fieldNode.get(); diff --git a/src/core/lombok/javac/handlers/HandleSuperBuilder.java b/src/core/lombok/javac/handlers/HandleSuperBuilder.java index c5a50508..a48225fa 100644 --- a/src/core/lombok/javac/handlers/HandleSuperBuilder.java +++ b/src/core/lombok/javac/handlers/HandleSuperBuilder.java @@ -869,10 +869,11 @@ public class HandleSuperBuilder extends JavacAnnotationHandler { } else { methodDef = maker.MethodDef(modifiers, job.toName(job.buildMethodName), cloneSelfType(job.parentType), List.nil(), List.nil(), thrownExceptions, body, null); } + addCheckReturnValue(methodDef.mods, job.builderType, job.sourceNode); createRelevantNonNullAnnotation(job.builderType, methodDef); return methodDef; } - + private JCMethodDecl generateCleanMethod(java.util.List builderFields, JavacNode type, JavacNode source) { JavacTreeMaker maker = type.getTreeMaker(); ListBuffer statements = new ListBuffer(); diff --git a/src/core/lombok/javac/handlers/HandleWith.java b/src/core/lombok/javac/handlers/HandleWith.java index 52f2c192..395cae65 100644 --- a/src/core/lombok/javac/handlers/HandleWith.java +++ b/src/core/lombok/javac/handlers/HandleWith.java @@ -281,15 +281,16 @@ public class HandleWith extends JavacAnnotationHandler { List annsOnMethod = copyAnnotations(onMethod, maker); CheckerFrameworkVersion checkerFramework = getCheckerFrameworkVersion(source); if (checkerFramework.generateSideEffectFree()) annsOnMethod = annsOnMethod.prepend(maker.Annotation(genTypeRef(source, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE), List.nil())); - + if (isFieldDeprecated(field)) annsOnMethod = annsOnMethod.prepend(maker.Annotation(genJavaLangTypeRef(field, "Deprecated"), List.nil())); - + if (makeAbstract) access |= Flags.ABSTRACT; AnnotationValues accessors = JavacHandlerUtil.getAccessorsForField(field); boolean makeFinal = shouldMakeFinal(field, accessors); if (makeFinal) access |= Flags.FINAL; JCMethodDecl decl = recursiveSetGeneratedBy(maker.MethodDef(maker.Modifiers(access, annsOnMethod), methodName, returnType, methodGenericParams, parameters, throwsClauses, methodBody, annotationMethodDefaultValue), source); + addCheckReturnValue(decl.mods, field, source); copyJavadoc(field, decl, CopyJavadoc.WITH); return decl; } diff --git a/src/core/lombok/javac/handlers/HandleWithBy.java b/src/core/lombok/javac/handlers/HandleWithBy.java index 7978928f..aad502a0 100644 --- a/src/core/lombok/javac/handlers/HandleWithBy.java +++ b/src/core/lombok/javac/handlers/HandleWithBy.java @@ -334,6 +334,7 @@ public class HandleWithBy extends JavacAnnotationHandler { createRelevantNonNullAnnotation(source, param); JCMethodDecl decl = recursiveSetGeneratedBy(maker.MethodDef(maker.Modifiers(access, annsOnMethod), methodName, returnType, methodGenericParams, parameters, throwsClauses, methodBody, annotationMethodDefaultValue), source); + addCheckReturnValue(decl.mods, field, source); copyJavadoc(field, decl, CopyJavadoc.WITH_BY); createRelevantNonNullAnnotation(source, decl); return decl; diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java index 0c075295..48a2c3fb 100644 --- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java +++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java @@ -1554,6 +1554,11 @@ public class JavacHandlerUtil { } } + public static void addCheckReturnValue(JCModifiers mods, JavacNode node, JavacNode source) { + if (Boolean.FALSE.equals(node.getAst().readConfiguration(ConfigurationKeys.ADD_CHECK_RETURN_VALUE_ANNOTATIONS))) return; + addAnnotation(mods, node, source, "lombok.CheckReturnValue", null); + } + public static void addAnnotation(JCModifiers mods, JavacNode node, JavacNode source, String annotationTypeFqn, JCExpression arg) { boolean isJavaLangBased; String simpleName; { diff --git a/test/transform/resource/after-delombok/CheckReturnValueBuilder.java b/test/transform/resource/after-delombok/CheckReturnValueBuilder.java new file mode 100644 index 00000000..c8d84291 --- /dev/null +++ b/test/transform/resource/after-delombok/CheckReturnValueBuilder.java @@ -0,0 +1,59 @@ +class CheckReturnValueBuilder { + private final int x; + private final String name; + @java.lang.SuppressWarnings("all") + @lombok.Generated + CheckReturnValueBuilder(final int x, final String name) { + this.x = x; + this.name = name; + } + @java.lang.SuppressWarnings("all") + @lombok.Generated + public static class CheckReturnValueBuilderBuilder { + @java.lang.SuppressWarnings("all") + @lombok.Generated + private int x; + @java.lang.SuppressWarnings("all") + @lombok.Generated + private String name; + @java.lang.SuppressWarnings("all") + @lombok.Generated + CheckReturnValueBuilderBuilder() { + } + /** + * @return {@code this}. + */ + @java.lang.SuppressWarnings("all") + @lombok.Generated + public CheckReturnValueBuilder.CheckReturnValueBuilderBuilder x(final int x) { + this.x = x; + return this; + } + /** + * @return {@code this}. + */ + @java.lang.SuppressWarnings("all") + @lombok.Generated + public CheckReturnValueBuilder.CheckReturnValueBuilderBuilder name(final String name) { + this.name = name; + return this; + } + @lombok.CheckReturnValue + @java.lang.SuppressWarnings("all") + @lombok.Generated + public CheckReturnValueBuilder build() { + return new CheckReturnValueBuilder(this.x, this.name); + } + @java.lang.Override + @java.lang.SuppressWarnings("all") + @lombok.Generated + public java.lang.String toString() { + return "CheckReturnValueBuilder.CheckReturnValueBuilderBuilder(x=" + this.x + ", name=" + this.name + ")"; + } + } + @java.lang.SuppressWarnings("all") + @lombok.Generated + public static CheckReturnValueBuilder.CheckReturnValueBuilderBuilder builder() { + return new CheckReturnValueBuilder.CheckReturnValueBuilderBuilder(); + } +} diff --git a/test/transform/resource/after-delombok/CheckReturnValueOff.java b/test/transform/resource/after-delombok/CheckReturnValueOff.java new file mode 100644 index 00000000..ecffc92e --- /dev/null +++ b/test/transform/resource/after-delombok/CheckReturnValueOff.java @@ -0,0 +1,14 @@ +class CheckReturnValueOff { + final int x; + CheckReturnValueOff(int x) { + this.x = x; + } + /** + * @return a clone of this object, except with this updated property (returns {@code this} if an identical value is passed). + */ + @java.lang.SuppressWarnings("all") + @lombok.Generated + public CheckReturnValueOff withX(final int x) { + return this.x == x ? this : new CheckReturnValueOff(x); + } +} diff --git a/test/transform/resource/after-delombok/CheckReturnValueWith.java b/test/transform/resource/after-delombok/CheckReturnValueWith.java new file mode 100644 index 00000000..a679b4b2 --- /dev/null +++ b/test/transform/resource/after-delombok/CheckReturnValueWith.java @@ -0,0 +1,26 @@ +class CheckReturnValueWith { + final int x; + final String name; + CheckReturnValueWith(int x, String name) { + this.x = x; + this.name = name; + } + /** + * @return a clone of this object, except with this updated property (returns {@code this} if an identical value is passed). + */ + @lombok.CheckReturnValue + @java.lang.SuppressWarnings("all") + @lombok.Generated + public CheckReturnValueWith withX(final int x) { + return this.x == x ? this : new CheckReturnValueWith(x, this.name); + } + /** + * @return a clone of this object, except with this updated property (returns {@code this} if an identical value is passed). + */ + @lombok.CheckReturnValue + @java.lang.SuppressWarnings("all") + @lombok.Generated + public CheckReturnValueWith withName(final String name) { + return this.name == name ? this : new CheckReturnValueWith(this.x, name); + } +} diff --git a/test/transform/resource/after-ecj/CheckReturnValueBuilder.java b/test/transform/resource/after-ecj/CheckReturnValueBuilder.java new file mode 100644 index 00000000..d7da093d --- /dev/null +++ b/test/transform/resource/after-ecj/CheckReturnValueBuilder.java @@ -0,0 +1,39 @@ +@lombok.Builder class CheckReturnValueBuilder { + public static @java.lang.SuppressWarnings("all") @lombok.Generated class CheckReturnValueBuilderBuilder { + private @java.lang.SuppressWarnings("all") @lombok.Generated int x; + private @java.lang.SuppressWarnings("all") @lombok.Generated String name; + @java.lang.SuppressWarnings("all") @lombok.Generated CheckReturnValueBuilderBuilder() { + super(); + } + /** + * @return {@code this}. + */ + public @java.lang.SuppressWarnings("all") @lombok.Generated CheckReturnValueBuilder.CheckReturnValueBuilderBuilder x(final int x) { + this.x = x; + return this; + } + /** + * @return {@code this}. + */ + public @java.lang.SuppressWarnings("all") @lombok.Generated CheckReturnValueBuilder.CheckReturnValueBuilderBuilder name(final String name) { + this.name = name; + return this; + } + public @lombok.CheckReturnValue @java.lang.SuppressWarnings("all") @lombok.Generated CheckReturnValueBuilder build() { + return new CheckReturnValueBuilder(this.x, this.name); + } + public @java.lang.Override @java.lang.SuppressWarnings("all") @lombok.Generated java.lang.String toString() { + return (((("CheckReturnValueBuilder.CheckReturnValueBuilderBuilder(x=" + this.x) + ", name=") + this.name) + ")"); + } + } + private final int x; + private final String name; + @java.lang.SuppressWarnings("all") @lombok.Generated CheckReturnValueBuilder(final int x, final String name) { + super(); + this.x = x; + this.name = name; + } + public static @java.lang.SuppressWarnings("all") @lombok.Generated CheckReturnValueBuilder.CheckReturnValueBuilderBuilder builder() { + return new CheckReturnValueBuilder.CheckReturnValueBuilderBuilder(); + } +} diff --git a/test/transform/resource/after-ecj/CheckReturnValueOff.java b/test/transform/resource/after-ecj/CheckReturnValueOff.java new file mode 100644 index 00000000..b786cdbc --- /dev/null +++ b/test/transform/resource/after-ecj/CheckReturnValueOff.java @@ -0,0 +1,14 @@ +import lombok.With; +class CheckReturnValueOff { + final @With int x; + CheckReturnValueOff(int x) { + super(); + this.x = x; + } + /** + * @return a clone of this object, except with this updated property (returns {@code this} if an identical value is passed). + */ + public @java.lang.SuppressWarnings("all") @lombok.Generated CheckReturnValueOff withX(final int x) { + return ((this.x == x) ? this : new CheckReturnValueOff(x)); + } +} diff --git a/test/transform/resource/after-ecj/CheckReturnValueWith.java b/test/transform/resource/after-ecj/CheckReturnValueWith.java new file mode 100644 index 00000000..c798870a --- /dev/null +++ b/test/transform/resource/after-ecj/CheckReturnValueWith.java @@ -0,0 +1,22 @@ +import lombok.With; +class CheckReturnValueWith { + final @With int x; + final @With String name; + CheckReturnValueWith(int x, String name) { + super(); + this.x = x; + this.name = name; + } + /** + * @return a clone of this object, except with this updated property (returns {@code this} if an identical value is passed). + */ + public @lombok.CheckReturnValue @java.lang.SuppressWarnings("all") @lombok.Generated CheckReturnValueWith withX(final int x) { + return ((this.x == x) ? this : new CheckReturnValueWith(x, this.name)); + } + /** + * @return a clone of this object, except with this updated property (returns {@code this} if an identical value is passed). + */ + public @lombok.CheckReturnValue @java.lang.SuppressWarnings("all") @lombok.Generated CheckReturnValueWith withName(final String name) { + return ((this.name == name) ? this : new CheckReturnValueWith(this.x, name)); + } +} diff --git a/test/transform/resource/before/CheckReturnValueBuilder.java b/test/transform/resource/before/CheckReturnValueBuilder.java new file mode 100644 index 00000000..1c8cb7e2 --- /dev/null +++ b/test/transform/resource/before/CheckReturnValueBuilder.java @@ -0,0 +1,5 @@ +@lombok.Builder +class CheckReturnValueBuilder { + private final int x; + private final String name; +} diff --git a/test/transform/resource/before/CheckReturnValueOff.java b/test/transform/resource/before/CheckReturnValueOff.java new file mode 100644 index 00000000..3a4aebca --- /dev/null +++ b/test/transform/resource/before/CheckReturnValueOff.java @@ -0,0 +1,9 @@ +//CONF: lombok.addCheckReturnValueAnnotation = false +import lombok.With; +class CheckReturnValueOff { + @With final int x; + + CheckReturnValueOff(int x) { + this.x = x; + } +} diff --git a/test/transform/resource/before/CheckReturnValueWith.java b/test/transform/resource/before/CheckReturnValueWith.java new file mode 100644 index 00000000..187a96cb --- /dev/null +++ b/test/transform/resource/before/CheckReturnValueWith.java @@ -0,0 +1,10 @@ +import lombok.With; +class CheckReturnValueWith { + @With final int x; + @With final String name; + + CheckReturnValueWith(int x, String name) { + this.x = x; + this.name = name; + } +} From 14f52226b6121d9f49e49add909949e44958c198 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Sun, 22 Feb 2026 19:35:06 +0100 Subject: [PATCH 2/9] [feature][`@CheckReturnValue`] Make @CheckReturnValue opt-in rather than opt-out to avoid breaking existing tests. --- src/core/lombok/CheckReturnValue.java | 2 +- src/core/lombok/ConfigurationKeys.java | 2 +- src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java | 2 +- src/core/lombok/javac/handlers/JavacHandlerUtil.java | 2 +- test/transform/resource/before/CheckReturnValueBuilder.java | 1 + test/transform/resource/before/CheckReturnValueWith.java | 1 + 6 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/core/lombok/CheckReturnValue.java b/src/core/lombok/CheckReturnValue.java index ef8f5c61..1a8f2dcb 100644 --- a/src/core/lombok/CheckReturnValue.java +++ b/src/core/lombok/CheckReturnValue.java @@ -35,7 +35,7 @@ import java.lang.annotation.Target; * Static analysis tools (Error Prone, IntelliJ, SpotBugs) recognize {@code @CheckReturnValue} * by simple class name, regardless of package, and will warn when the return value is discarded. *

- * If you want to opt out, you can add {@code lombok.addCheckReturnValueAnnotation = false} to + * If you want to opt in, you can add {@code lombok.addCheckReturnValueAnnotation = true} to * {@code lombok.config}. */ @Target({ElementType.METHOD}) diff --git a/src/core/lombok/ConfigurationKeys.java b/src/core/lombok/ConfigurationKeys.java index 6c5c08f5..321b1de6 100644 --- a/src/core/lombok/ConfigurationKeys.java +++ b/src/core/lombok/ConfigurationKeys.java @@ -96,7 +96,7 @@ public class ConfigurationKeys { * If {@code true}, lombok generates {@code @lombok.CheckReturnValue} on generated methods where the return value should not be ignored, * such as {@code @With} methods and {@code @Builder}'s {@code build()} method. */ - public static final ConfigurationKey ADD_CHECK_RETURN_VALUE_ANNOTATIONS = new ConfigurationKey("lombok.addCheckReturnValueAnnotation", "Generate @lombok.CheckReturnValue on generated methods where the return value should not be ignored (default: true).") {}; + public static final ConfigurationKey ADD_CHECK_RETURN_VALUE_ANNOTATIONS = new ConfigurationKey("lombok.addCheckReturnValueAnnotation", "Generate @lombok.CheckReturnValue on generated methods where the return value should not be ignored (default: false).") {}; /** * lombok configuration: {@code lombok.extern.findbugs.addSuppressFBWarnings} = {@code true} | {@code false}. diff --git a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java index a479356d..8255d1cb 100644 --- a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java +++ b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java @@ -2128,7 +2128,7 @@ public class EclipseHandlerUtil { } public static Annotation[] addCheckReturnValue(EclipseNode node, ASTNode source, Annotation[] originalAnnotationArray) { - if (Boolean.FALSE.equals(node.getAst().readConfiguration(ConfigurationKeys.ADD_CHECK_RETURN_VALUE_ANNOTATIONS))) return originalAnnotationArray; + if (!Boolean.TRUE.equals(node.getAst().readConfiguration(ConfigurationKeys.ADD_CHECK_RETURN_VALUE_ANNOTATIONS))) return originalAnnotationArray; return addAnnotation(source, originalAnnotationArray, LOMBOK_CHECK_RETURN_VALUE); } diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java index 48a2c3fb..722bb436 100644 --- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java +++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java @@ -1555,7 +1555,7 @@ public class JavacHandlerUtil { } public static void addCheckReturnValue(JCModifiers mods, JavacNode node, JavacNode source) { - if (Boolean.FALSE.equals(node.getAst().readConfiguration(ConfigurationKeys.ADD_CHECK_RETURN_VALUE_ANNOTATIONS))) return; + if (!Boolean.TRUE.equals(node.getAst().readConfiguration(ConfigurationKeys.ADD_CHECK_RETURN_VALUE_ANNOTATIONS))) return; addAnnotation(mods, node, source, "lombok.CheckReturnValue", null); } diff --git a/test/transform/resource/before/CheckReturnValueBuilder.java b/test/transform/resource/before/CheckReturnValueBuilder.java index 1c8cb7e2..a4a904ee 100644 --- a/test/transform/resource/before/CheckReturnValueBuilder.java +++ b/test/transform/resource/before/CheckReturnValueBuilder.java @@ -1,3 +1,4 @@ +//CONF: lombok.addCheckReturnValueAnnotation = true @lombok.Builder class CheckReturnValueBuilder { private final int x; diff --git a/test/transform/resource/before/CheckReturnValueWith.java b/test/transform/resource/before/CheckReturnValueWith.java index 187a96cb..167aaae8 100644 --- a/test/transform/resource/before/CheckReturnValueWith.java +++ b/test/transform/resource/before/CheckReturnValueWith.java @@ -1,3 +1,4 @@ +//CONF: lombok.addCheckReturnValueAnnotation = true import lombok.With; class CheckReturnValueWith { @With final int x; From dc1ba17b4d0621cd21f688a82805aac358b24b5d Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Thu, 30 Apr 2026 00:21:55 +0200 Subject: [PATCH 3/9] [feature][`@CheckReturnValue`] Replace boolean opt-in with enum-valued `lombok.checkReturnValueAnnotation` config key. Per PR #4013 review, swap the boolean `lombok.addCheckReturnValueAnnotation` for an enum-valued `lombok.checkReturnValueAnnotation` (values: `none`, `lombok`; default: `none`). This leaves room for additional flavors (`jetbrains`, `errorprone`, `custom: FQN`) in future releases without another key rename, and for flipping the default to `lombok` after a release cycle or two. # Conflicts: # doc/changelog.markdown --- doc/changelog.markdown | 1 + src/core/lombok/CheckReturnValue.java | 2 +- src/core/lombok/ConfigurationKeys.java | 8 +++--- .../configuration/CheckReturnValueFlavor.java | 27 +++++++++++++++++++ .../eclipse/handlers/EclipseHandlerUtil.java | 12 +++++++-- .../javac/handlers/JavacHandlerUtil.java | 13 +++++++-- .../before/CheckReturnValueBuilder.java | 2 +- .../resource/before/CheckReturnValueOff.java | 2 +- .../resource/before/CheckReturnValueWith.java | 2 +- website/templates/features/configuration.html | 6 +++++ 10 files changed, 64 insertions(+), 11 deletions(-) create mode 100644 src/core/lombok/core/configuration/CheckReturnValueFlavor.java diff --git a/doc/changelog.markdown b/doc/changelog.markdown index 7375d310..c9ea2572 100644 --- a/doc/changelog.markdown +++ b/doc/changelog.markdown @@ -3,6 +3,7 @@ Lombok Changelog ### v1.18.47 "Edgy Guinea Pig" * BUGFIX: `@SneakyThrows` usage on JDK26 no longer results in class files that require `lombok.jar` to be on the runtime classpath (which should not be neccessary). [#4040](https://github.com/projectlombok/lombok/issues/4022). +* FEATURE: New [config key](https://projectlombok.org/features/configuration) `lombok.checkReturnValueAnnotation` (values: `none`, `lombok`; default: `none`) lets lombok generate `@lombok.CheckReturnValue` on generated methods where the return value should not be ignored, such as `@With` methods and `@Builder.build()`. A future lombok release may flip the default to `lombok`. [#4013](https://github.com/projectlombok/lombok/pull/4013). * PROMOTION: `@SuperBuilder` has been promoted to the main package. Otherwise, no changes have been made to the annotation. The old experimental annotation will remain for a few versions, at which point it will be marked as a deprecated annotation. Eventually it'll be removed. If you had `lombok.config` configuration for this annotation, the configuration keys for this feature have been renamed. * OLD-CRUFT: `lombok.experimental.Wither` and `lombok.Delegate` are deprecated remnants; these features were moved (to respectively `lombok.With` and `lombok.experimental.Delegate` over 5 years ago. They are now removed entirely. If your project is dependent on an older version of lombok which still has those; fret not, lombok still processes these annotations. It just no longer includes them in the jar. diff --git a/src/core/lombok/CheckReturnValue.java b/src/core/lombok/CheckReturnValue.java index 1a8f2dcb..0f6ae868 100644 --- a/src/core/lombok/CheckReturnValue.java +++ b/src/core/lombok/CheckReturnValue.java @@ -35,7 +35,7 @@ import java.lang.annotation.Target; * Static analysis tools (Error Prone, IntelliJ, SpotBugs) recognize {@code @CheckReturnValue} * by simple class name, regardless of package, and will warn when the return value is discarded. *

- * If you want to opt in, you can add {@code lombok.addCheckReturnValueAnnotation = true} to + * If you want to opt in, you can add {@code lombok.checkReturnValueAnnotation = lombok} to * {@code lombok.config}. */ @Target({ElementType.METHOD}) diff --git a/src/core/lombok/ConfigurationKeys.java b/src/core/lombok/ConfigurationKeys.java index 321b1de6..9dafc083 100644 --- a/src/core/lombok/ConfigurationKeys.java +++ b/src/core/lombok/ConfigurationKeys.java @@ -25,6 +25,7 @@ import java.util.List; import lombok.core.configuration.CallSuperType; import lombok.core.configuration.CapitalizationStrategy; +import lombok.core.configuration.CheckReturnValueFlavor; import lombok.core.configuration.CheckerFrameworkVersion; import lombok.core.configuration.ConfigurationKey; import lombok.core.configuration.FlagUsageType; @@ -91,12 +92,13 @@ public class ConfigurationKeys { public static final ConfigurationKey ADD_LOMBOK_GENERATED_ANNOTATIONS = new ConfigurationKey("lombok.addLombokGeneratedAnnotation", "Generate @lombok.Generated on all generated code (default: true).") {}; /** - * lombok configuration: {@code lombok.addCheckReturnValueAnnotation} = {@code true} | {@code false}. + * lombok configuration: {@code lombok.checkReturnValueAnnotation} = [{@code none} | {@code lombok}]. * - * If {@code true}, lombok generates {@code @lombok.CheckReturnValue} on generated methods where the return value should not be ignored, + * If set to {@code lombok}, lombok generates {@code @lombok.CheckReturnValue} on generated methods where the return value should not be ignored, * such as {@code @With} methods and {@code @Builder}'s {@code build()} method. + * If set to {@code none} (the current default), no such annotation is emitted. A future lombok release may flip the default to {@code lombok}. */ - public static final ConfigurationKey ADD_CHECK_RETURN_VALUE_ANNOTATIONS = new ConfigurationKey("lombok.addCheckReturnValueAnnotation", "Generate @lombok.CheckReturnValue on generated methods where the return value should not be ignored (default: false).") {}; + public static final ConfigurationKey CHECK_RETURN_VALUE_ANNOTATION = new ConfigurationKey("lombok.checkReturnValueAnnotation", "Which @CheckReturnValue annotation flavor to emit on generated methods. Values: none, lombok (default: none).") {}; /** * lombok configuration: {@code lombok.extern.findbugs.addSuppressFBWarnings} = {@code true} | {@code false}. diff --git a/src/core/lombok/core/configuration/CheckReturnValueFlavor.java b/src/core/lombok/core/configuration/CheckReturnValueFlavor.java new file mode 100644 index 00000000..b313ee73 --- /dev/null +++ b/src/core/lombok/core/configuration/CheckReturnValueFlavor.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 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 + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.core.configuration; + +/** Used for lombok configuration to select which {@code @CheckReturnValue} annotation flavor to emit on generated methods. */ +public enum CheckReturnValueFlavor { + NONE, LOMBOK; +} diff --git a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java index 8255d1cb..df0bb234 100644 --- a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java +++ b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java @@ -116,6 +116,7 @@ import lombok.core.AnnotationValues; import lombok.core.AnnotationValues.AnnotationValue; import lombok.core.LombokImmutableList; import lombok.core.TypeResolver; +import lombok.core.configuration.CheckReturnValueFlavor; import lombok.core.configuration.CheckerFrameworkVersion; import lombok.core.configuration.NullAnnotationLibrary; import lombok.core.configuration.NullCheckExceptionType; @@ -2128,8 +2129,15 @@ public class EclipseHandlerUtil { } public static Annotation[] addCheckReturnValue(EclipseNode node, ASTNode source, Annotation[] originalAnnotationArray) { - if (!Boolean.TRUE.equals(node.getAst().readConfiguration(ConfigurationKeys.ADD_CHECK_RETURN_VALUE_ANNOTATIONS))) return originalAnnotationArray; - return addAnnotation(source, originalAnnotationArray, LOMBOK_CHECK_RETURN_VALUE); + CheckReturnValueFlavor flavor = node.getAst().readConfiguration(ConfigurationKeys.CHECK_RETURN_VALUE_ANNOTATION); + if (flavor == null) flavor = CheckReturnValueFlavor.NONE; + switch (flavor) { + case LOMBOK: + return addAnnotation(source, originalAnnotationArray, LOMBOK_CHECK_RETURN_VALUE); + case NONE: + default: + return originalAnnotationArray; + } } static Annotation[] addAnnotation(ASTNode source, Annotation[] originalAnnotationArray, char[][] annotationTypeFqn) { diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java index 722bb436..0f134b8e 100644 --- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java +++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java @@ -96,6 +96,7 @@ import lombok.core.AnnotationValues.AnnotationValue; import lombok.core.CleanupTask; import lombok.core.LombokImmutableList; import lombok.core.TypeResolver; +import lombok.core.configuration.CheckReturnValueFlavor; import lombok.core.configuration.CheckerFrameworkVersion; import lombok.core.configuration.NullAnnotationLibrary; import lombok.core.configuration.NullCheckExceptionType; @@ -1555,8 +1556,16 @@ public class JavacHandlerUtil { } public static void addCheckReturnValue(JCModifiers mods, JavacNode node, JavacNode source) { - if (!Boolean.TRUE.equals(node.getAst().readConfiguration(ConfigurationKeys.ADD_CHECK_RETURN_VALUE_ANNOTATIONS))) return; - addAnnotation(mods, node, source, "lombok.CheckReturnValue", null); + CheckReturnValueFlavor flavor = node.getAst().readConfiguration(ConfigurationKeys.CHECK_RETURN_VALUE_ANNOTATION); + if (flavor == null) flavor = CheckReturnValueFlavor.NONE; + switch (flavor) { + case LOMBOK: + addAnnotation(mods, node, source, "lombok.CheckReturnValue", null); + return; + case NONE: + default: + return; + } } public static void addAnnotation(JCModifiers mods, JavacNode node, JavacNode source, String annotationTypeFqn, JCExpression arg) { diff --git a/test/transform/resource/before/CheckReturnValueBuilder.java b/test/transform/resource/before/CheckReturnValueBuilder.java index a4a904ee..83c9e551 100644 --- a/test/transform/resource/before/CheckReturnValueBuilder.java +++ b/test/transform/resource/before/CheckReturnValueBuilder.java @@ -1,4 +1,4 @@ -//CONF: lombok.addCheckReturnValueAnnotation = true +//CONF: lombok.checkReturnValueAnnotation = lombok @lombok.Builder class CheckReturnValueBuilder { private final int x; diff --git a/test/transform/resource/before/CheckReturnValueOff.java b/test/transform/resource/before/CheckReturnValueOff.java index 3a4aebca..7197fc75 100644 --- a/test/transform/resource/before/CheckReturnValueOff.java +++ b/test/transform/resource/before/CheckReturnValueOff.java @@ -1,4 +1,4 @@ -//CONF: lombok.addCheckReturnValueAnnotation = false +//CONF: lombok.checkReturnValueAnnotation = none import lombok.With; class CheckReturnValueOff { @With final int x; diff --git a/test/transform/resource/before/CheckReturnValueWith.java b/test/transform/resource/before/CheckReturnValueWith.java index 167aaae8..6ed0dba9 100644 --- a/test/transform/resource/before/CheckReturnValueWith.java +++ b/test/transform/resource/before/CheckReturnValueWith.java @@ -1,4 +1,4 @@ -//CONF: lombok.addCheckReturnValueAnnotation = true +//CONF: lombok.checkReturnValueAnnotation = lombok import lombok.With; class CheckReturnValueWith { @With final int x; diff --git a/website/templates/features/configuration.html b/website/templates/features/configuration.html index 6675d61e..a09b0c81 100644 --- a/website/templates/features/configuration.html +++ b/website/templates/features/configuration.html @@ -89,6 +89,12 @@ Many flavors are available: jspecify (recommended), checkerframework (recommended), jakarta, eclipse, jetbrains, netbeans, androidx, findbugs, spring, jml, javax (=JSR305; not recommended), android.support (deprecated within android), or define your own via CUSTOM:fully.qualified.NonNullAnnotation:fully.qualified.NullableAnnotation; if your nullity annotation is solely of the type use style (it annotates types, such as eclipse's and checkerframework's offerings, versus annotating methods and parameters), the format is CUSTOM:TYPE_USE:nonnullanno:nullableanno.
This feature was added in lombok v1.18.12.
+

+ Lombok can add a @CheckReturnValue annotation to generated methods whose return value should not be ignored, such as withX methods and @Builder's build() method. Static analysis tools (IntelliJ, Error Prone, SpotBugs) will then warn when callers discard the result. By default, no such annotation is added. Enable with: +

    +
  1. lombok.checkReturnValueAnnotation = <flavor>
  2. +
+ Currently the only non-none flavor is lombok, which emits @lombok.CheckReturnValue. A future lombok release may flip the default to lombok.

Lombok can be configured to add @lombok.Generated annotations to all generated nodes where possible; useful for JaCoCo (which has built in support), or other style checkers and code coverage tools: From 54bc6c24d361b9000afeeeaead65a4bc2f4b23bd Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Fri, 7 Aug 2026 10:13:30 +0200 Subject: [PATCH 4/9] [feature][@CheckReturnValue] Add Tim te Beek to AUTHORS --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 4a6ef2fd..9ee773f5 100755 --- a/AUTHORS +++ b/AUTHORS @@ -63,6 +63,7 @@ Taiki Sugawara Takuya Murakami Thomas Darimont Till Brychcy +Tim te Beek Victor Williams Stafusa da Silva Yonatan Sherwin Yun Zhi Lin From 5aa26873fd69607535cd1a33762a19719f46e850 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Fri, 21 Aug 2026 02:36:12 +0200 Subject: [PATCH 5/9] [checkerframework] `@SEF` now added to generated constructors; `@Builder`'s `build()` no longer `@SEF` if building for a non-SEF method. --- doc/changelog.markdown | 2 ++ src/core/lombok/core/handlers/HandlerUtil.java | 9 ++++++++- .../eclipse/handlers/EclipseHandlerUtil.java | 11 +++++++++++ .../lombok/eclipse/handlers/HandleBuilder.java | 10 +++++++++- .../eclipse/handlers/HandleConstructor.java | 10 ++++++++-- .../handlers/HandleStandardException.java | 13 +++++++++++++ .../eclipse/handlers/HandleSuperBuilder.java | 2 +- .../lombok/javac/handlers/HandleBuilder.java | 11 ++++++++++- .../lombok/javac/handlers/HandleConstructor.java | 16 +++++++++++++--- .../javac/handlers/HandleStandardException.java | 8 +++++++- .../lombok/javac/handlers/JavacHandlerUtil.java | 10 ++++++++++ 11 files changed, 92 insertions(+), 10 deletions(-) diff --git a/doc/changelog.markdown b/doc/changelog.markdown index c9ea2572..af8894f1 100644 --- a/doc/changelog.markdown +++ b/doc/changelog.markdown @@ -6,6 +6,8 @@ Lombok Changelog * FEATURE: New [config key](https://projectlombok.org/features/configuration) `lombok.checkReturnValueAnnotation` (values: `none`, `lombok`; default: `none`) lets lombok generate `@lombok.CheckReturnValue` on generated methods where the return value should not be ignored, such as `@With` methods and `@Builder.build()`. A future lombok release may flip the default to `lombok`. [#4013](https://github.com/projectlombok/lombok/pull/4013). * PROMOTION: `@SuperBuilder` has been promoted to the main package. Otherwise, no changes have been made to the annotation. The old experimental annotation will remain for a few versions, at which point it will be marked as a deprecated annotation. Eventually it'll be removed. If you had `lombok.config` configuration for this annotation, the configuration keys for this feature have been renamed. * OLD-CRUFT: `lombok.experimental.Wither` and `lombok.Delegate` are deprecated remnants; these features were moved (to respectively `lombok.With` and `lombok.experimental.Delegate` over 5 years ago. They are now removed entirely. If your project is dependent on an older version of lombok which still has those; fret not, lombok still processes these annotations. It just no longer includes them in the jar. +* FEATURE: CheckerFramework: Lombok now adds `@SideEffectFree` to constructors it makes if you have enabled checker framework via `lombok.config`. +* BUGFIX: CheckerFramework: Lombok would add `@SideEffectFree` to the `build()` method of any generated builder, even if it is a builder for invoking a method; in that case, the side-effect-free nature of `build()` mirrors the side-effect-free nature of the method invocation you've built. Lombok now checks if the method it generated a builder for is side effect free / 'check return type'. ### v1.18.46 (April 22nd, 2026) * PLATFORM: JDK26 support added [#4019](https://github.com/projectlombok/lombok/issues/4019). diff --git a/src/core/lombok/core/handlers/HandlerUtil.java b/src/core/lombok/core/handlers/HandlerUtil.java index 4f546352..f9dd3351 100644 --- a/src/core/lombok/core/handlers/HandlerUtil.java +++ b/src/core/lombok/core/handlers/HandlerUtil.java @@ -79,7 +79,7 @@ public class HandlerUtil { return 43; } - public static final List NONNULL_ANNOTATIONS, BASE_COPYABLE_ANNOTATIONS, JACKSON_COPY_TO_GETTER_ANNOTATIONS, JACKSON_COPY_TO_SETTER_ANNOTATIONS, JACKSON_COPY_TO_BUILDER_SINGULAR_SETTER_ANNOTATIONS, JACKSON_COPY_TO_BUILDER_ANNOTATIONS; + public static final List NONNULL_ANNOTATIONS, BASE_COPYABLE_ANNOTATIONS, JACKSON_COPY_TO_GETTER_ANNOTATIONS, JACKSON_COPY_TO_SETTER_ANNOTATIONS, JACKSON_COPY_TO_BUILDER_SINGULAR_SETTER_ANNOTATIONS, JACKSON_COPY_TO_BUILDER_ANNOTATIONS, IMPLIES_CHECK_RETURN_VALUE_ANNOTATIONS; static { // This is a list of annotations with a __highly specific meaning__: All annotations in this list indicate that passing null for the relevant item is __never__ acceptable, regardless of settings or circumstance. // In other words, things like 'this models a database table, and the db table column has a nonnull constraint', or 'this represents a web form, and if this is null, the form is invalid' __do not count__ and should not be in this list; @@ -497,6 +497,13 @@ public class HandlerUtil { "com.fasterxml.jackson.databind.annotation.JsonNaming", "tools.jackson.databind.annotation.JsonNaming", })); + IMPLIES_CHECK_RETURN_VALUE_ANNOTATIONS = Collections.unmodifiableList(Arrays.asList(new String[] { + "lombok.CheckReturnValue", + "android.annotation.CheckResult", + "androidx.annotation.CheckResult", + "org.checkerframework.dataflow.qual.SideEffectFree", + "org.checkerframework.dataflow.qual.Pure", + })); } /** Checks if the given name is a valid identifier. diff --git a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java index df0bb234..a8383898 100644 --- a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java +++ b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java @@ -791,6 +791,17 @@ public class EclipseHandlerUtil { return false; } + public static boolean hasCheckReturnValueImplicatingAnnotations(EclipseNode node, Annotation[] anns) { + if (anns == null) return false; + for (Annotation annotation : anns) { + TypeReference typeRef = annotation.type; + if (typeRef != null && typeRef.getTypeName() != null) { + for (String bn : IMPLIES_CHECK_RETURN_VALUE_ANNOTATIONS) if (typeMatches(bn, node, typeRef)) return true; + } + } + return false; + } + private static final Annotation[] EMPTY_ANNOTATIONS_ARRAY = new Annotation[0]; /** diff --git a/src/core/lombok/eclipse/handlers/HandleBuilder.java b/src/core/lombok/eclipse/handlers/HandleBuilder.java index d1e31617..750a6b21 100755 --- a/src/core/lombok/eclipse/handlers/HandleBuilder.java +++ b/src/core/lombok/eclipse/handlers/HandleBuilder.java @@ -125,6 +125,7 @@ public class HandleBuilder extends EclipseAnnotationHandler { static class BuilderJob { CheckerFrameworkVersion checkerFramework; EclipseNode parentType; + boolean checkReturnValue; String builderMethodName, buildMethodName; boolean isStatic; TypeParameter[] typeParams; @@ -299,6 +300,7 @@ public class HandleBuilder extends EclipseAnnotationHandler { } if (parent.get() instanceof TypeDeclaration) { + job.checkReturnValue = true; if (!isClass(parent) && !isRecord(parent)) { annotationNode.addError(BUILDER_NODE_NOT_SUPPORTED_ERR); return; @@ -366,6 +368,7 @@ public class HandleBuilder extends EclipseAnnotationHandler { job.setBuilderClassName(job.replaceBuilderClassName(td.name)); if (!checkName("builderClassName", job.builderClassName, annotationNode)) return; } else if (parent.get() instanceof ConstructorDeclaration) { + job.checkReturnValue = true; ConstructorDeclaration cd = (ConstructorDeclaration) parent.get(); if (cd.typeParameters != null && cd.typeParameters.length > 0) { annotationNode.addError("@Builder is not supported on constructors with constructor type parameters."); @@ -382,6 +385,11 @@ public class HandleBuilder extends EclipseAnnotationHandler { if (!checkName("builderClassName", job.builderClassName, annotationNode)) return; } else if (parent.get() instanceof MethodDeclaration) { MethodDeclaration md = (MethodDeclaration) parent.get(); + if (md.returnType != null && Arrays.equals(md.returnType.getLastToken(), TypeReference.VOID)) { + job.checkReturnValue = false; + } else { + job.checkReturnValue = hasCheckReturnValueImplicatingAnnotations(parent, md.annotations); + } job.parentType = parent.up(); job.isStatic = md.isStatic(); @@ -885,7 +893,7 @@ public class HandleBuilder extends EclipseAnnotationHandler { } } out.statements = statements.isEmpty() ? null : statements.toArray(new Statement[0]); - if (job.checkerFramework.generateSideEffectFree()) { + if (job.checkReturnValue && job.checkerFramework.generateSideEffectFree()) { out.annotations = new Annotation[] {generateNamedAnnotation(job.source, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE)}; } out.receiver = generateBuildReceiver(job); diff --git a/src/core/lombok/eclipse/handlers/HandleConstructor.java b/src/core/lombok/eclipse/handlers/HandleConstructor.java index 19415696..9229fbe7 100755 --- a/src/core/lombok/eclipse/handlers/HandleConstructor.java +++ b/src/core/lombok/eclipse/handlers/HandleConstructor.java @@ -475,9 +475,12 @@ public class HandleConstructor { Annotation[] constructorProperties = null; if (addConstructorProperties && !isLocalType(type)) constructorProperties = createConstructorProperties(source, fieldsToParam); + Annotation[] checkerFramework = null; + if (getCheckerFrameworkVersion(type).generateSideEffectFree()) checkerFramework = new Annotation[] { generateNamedAnnotation(source, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE) }; + constructor.annotations = copyAnnotations(source, onConstructor.toArray(new Annotation[0]), - constructorProperties); + constructorProperties, checkerFramework); } constructor.traverse(new SetGeneratedByVisitor(source), typeDeclaration.scope); @@ -570,7 +573,10 @@ public class HandleConstructor { assigns.add(nameRef); Argument parameter = new Argument(field.name, fieldPos, copyType(field.type, source), Modifier.FINAL); - parameter.annotations = copyAnnotations(source, findCopyableAnnotations(fieldNode)); + Annotation[] checkerFramework = null; + if (getCheckerFrameworkVersion(type).generateSideEffectFree()) checkerFramework = new Annotation[] { generateNamedAnnotation(source, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE) }; + + parameter.annotations = copyAnnotations(source, findCopyableAnnotations(fieldNode), checkerFramework); if (parameter.annotations != null) { parameter.bits |= Eclipse.HasTypeAnnotations; constructor.bits |= Eclipse.HasTypeAnnotations; diff --git a/src/core/lombok/eclipse/handlers/HandleStandardException.java b/src/core/lombok/eclipse/handlers/HandleStandardException.java index 4672555c..e93c364b 100755 --- a/src/core/lombok/eclipse/handlers/HandleStandardException.java +++ b/src/core/lombok/eclipse/handlers/HandleStandardException.java @@ -25,6 +25,7 @@ import lombok.AccessLevel; import lombok.ConfigurationKeys; import lombok.experimental.StandardException; import lombok.core.AST.Kind; +import lombok.core.configuration.CheckerFrameworkVersion; import lombok.core.AnnotationValues; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; @@ -75,6 +76,9 @@ public class HandleStandardException extends EclipseAnnotationHandler { out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; out.returnType = new SingleTypeReference(classGenericName.toCharArray(), 0); Annotation overrideAnn = override ? makeMarkerAnnotation(TypeConstants.JAVA_LANG_OVERRIDE, job.source) : null; - Annotation sefAnn = job.checkerFramework.generateSideEffectFree() ? generateNamedAnnotation(job.source, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE): null; + Annotation sefAnn = job.checkerFramework.generateSideEffectFree() ? generateNamedAnnotation(job.source, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE) : null; if (overrideAnn != null && sefAnn != null) out.annotations = new Annotation[] {overrideAnn, sefAnn}; else if (overrideAnn != null) out.annotations = new Annotation[] {overrideAnn}; else if (sefAnn != null) out.annotations = new Annotation[] {sefAnn}; diff --git a/src/core/lombok/javac/handlers/HandleBuilder.java b/src/core/lombok/javac/handlers/HandleBuilder.java index ea3392e2..2b944f1d 100644 --- a/src/core/lombok/javac/handlers/HandleBuilder.java +++ b/src/core/lombok/javac/handlers/HandleBuilder.java @@ -29,6 +29,7 @@ import static lombok.javac.handlers.JavacHandlerUtil.*; import java.util.ArrayList; import javax.lang.model.element.Modifier; +import javax.lang.model.type.TypeKind; import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.tree.JCTree; @@ -105,6 +106,7 @@ public class HandleBuilder extends JavacAnnotationHandler { static class BuilderJob { CheckerFrameworkVersion checkerFramework; JavacNode parentType; + boolean checkReturnValue; String builderMethodName, buildMethodName; boolean isStatic; List typeParams; @@ -247,6 +249,7 @@ public class HandleBuilder extends JavacAnnotationHandler { } job.parentType = parent; + job.checkReturnValue = true; JCClassDecl td = (JCClassDecl) parent.get(); ListBuffer allFields = new ListBuffer(); @@ -315,6 +318,7 @@ public class HandleBuilder extends JavacAnnotationHandler { } job.parentType = parent.up(); + job.checkReturnValue = true; JCClassDecl td = (JCClassDecl) job.parentType.get(); job.typeParams = job.builderTypeParams = td.typarams; buildMethodReturnType = job.createBuilderParentTypeReference(); @@ -326,6 +330,11 @@ public class HandleBuilder extends JavacAnnotationHandler { job.parentType = parent.up(); JCClassDecl td = (JCClassDecl) job.parentType.get(); JCMethodDecl jmd = (JCMethodDecl) fillParametersFrom.get(); + if (jmd.restype instanceof JCPrimitiveTypeTree && ((JCPrimitiveTypeTree) jmd.restype).getPrimitiveTypeKind() == TypeKind.VOID) { + job.checkReturnValue = false; + } else { + job.checkReturnValue = hasCheckReturnValueImplicatingAnnotations(fillParametersFrom, jmd.mods.annotations); + } job.isStatic = (jmd.mods.flags & Flags.STATIC) != 0; JCExpression fullReturnType = jmd.restype; @@ -792,7 +801,7 @@ public class HandleBuilder extends JavacAnnotationHandler { JCBlock body = maker.Block(0, statements.toList()); - List annsOnMethod = job.checkerFramework.generateSideEffectFree() ? List.of(maker.Annotation(genTypeRef(job.builderType, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE), List.nil())) : List.nil(); + List annsOnMethod = (job.checkReturnValue && job.checkerFramework.generateSideEffectFree()) ? List.of(maker.Annotation(genTypeRef(job.builderType, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE), List.nil())) : List.nil(); JCVariableDecl recv = generateReceiver(job); JCMethodDecl methodDef; JCExpression returnTypeCopy = cloneType(maker, returnType, job.sourceNode); diff --git a/src/core/lombok/javac/handlers/HandleConstructor.java b/src/core/lombok/javac/handlers/HandleConstructor.java index ca885cd6..05571165 100644 --- a/src/core/lombok/javac/handlers/HandleConstructor.java +++ b/src/core/lombok/javac/handlers/HandleConstructor.java @@ -360,7 +360,12 @@ public class HandleConstructor { assigns.append(maker.Exec(assign)); } - JCModifiers mods = maker.Modifiers(toJavacModifier(level), List.nil()); + CheckerFrameworkVersion checkerFramework = getCheckerFrameworkVersion(source); + List anns = checkerFramework.generateSideEffectFree() ? + List.of(maker.Annotation(genTypeRef(source, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE), List.nil())) : + List.nil(); + + JCModifiers mods = maker.Modifiers(toJavacModifier(level), anns); if (addConstructorProperties && !isLocalType(typeNode) && LombokOptionsFactory.getDelombokOptions(typeNode.getContext()).getFormatPreferences().generateConstructorProperties()) { addConstructorProperties(mods, typeNode, fieldsToParam); } @@ -442,8 +447,6 @@ public class HandleConstructor { JavacTreeMaker maker = typeNode.getTreeMaker(); JCClassDecl type = (JCClassDecl) typeNode.get(); - JCModifiers mods = maker.Modifiers(Flags.STATIC | toJavacModifier(level)); - JCExpression returnType, constructorType; ListBuffer typeParams = new ListBuffer(); @@ -473,6 +476,13 @@ public class HandleConstructor { JCReturn returnStatement = maker.Return(maker.NewClass(null, List.nil(), constructorType, args.toList(), null)); JCBlock body = maker.Block(0, List.of(returnStatement)); + CheckerFrameworkVersion checkerFramework = getCheckerFrameworkVersion(source); + List anns = checkerFramework.generateSideEffectFree() ? + List.of(maker.Annotation(genTypeRef(source, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE), List.nil())) : + List.nil(); + + JCModifiers mods = maker.Modifiers(Flags.STATIC | toJavacModifier(level), anns); + JCMethodDecl methodDef = maker.MethodDef(mods, typeNode.toName(name), returnType, typeParams.toList(), params.toList(), List.nil(), body, null); createRelevantNonNullAnnotation(typeNode, methodDef); return recursiveSetGeneratedBy(methodDef, source); diff --git a/src/core/lombok/javac/handlers/HandleStandardException.java b/src/core/lombok/javac/handlers/HandleStandardException.java index 56bf8905..4b4df8e9 100644 --- a/src/core/lombok/javac/handlers/HandleStandardException.java +++ b/src/core/lombok/javac/handlers/HandleStandardException.java @@ -31,6 +31,7 @@ import lombok.AccessLevel; import lombok.ConfigurationKeys; import lombok.experimental.StandardException; import lombok.core.AST.Kind; +import lombok.core.configuration.CheckerFrameworkVersion; import lombok.core.AnnotationValues; import lombok.delombok.LombokOptionsFactory; import lombok.javac.Javac; @@ -205,7 +206,12 @@ public class HandleStandardException extends JavacAnnotationHandlernil()); + CheckerFrameworkVersion checkerFramework = getCheckerFrameworkVersion(source); + List anns = checkerFramework.generateSideEffectFree() ? + List.of(maker.Annotation(genTypeRef(source, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE), List.nil())) : + List.nil(); + + JCModifiers mods = maker.Modifiers(toJavacModifier(level), anns); if (addConstructorProperties) addConstructorProperties(mods, typeNode, msgParam, causeParam); return recursiveSetGeneratedBy(maker.MethodDef(mods, typeNode.toName(""), null, List.nil(), params.toList(), List.nil(), diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java index 0f134b8e..dc459d6e 100644 --- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java +++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java @@ -1744,6 +1744,16 @@ public class JavacHandlerUtil { return false; } + public static boolean hasCheckReturnValueImplicatingAnnotations(JavacNode node, List anns) { + if (anns == null) return false; + for (JCAnnotation ann : anns) { + String annotationTypeName = getTypeName(ann.annotationType); + for (String nn : IMPLIES_CHECK_RETURN_VALUE_ANNOTATIONS) if (typeMatches(nn, node, annotationTypeName)) return true; + } + + return false; + } + /** * Searches the given field node for annotations and returns each one that is 'copyable' (either via configuration or from the base list). */ From e76ba717219b593ac582901bbeb2e6e90b50bdff Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Fri, 21 Aug 2026 02:38:17 +0200 Subject: [PATCH 6/9] [config: `lombok.CheckReturnValue`]: setting is now a list of possible flavours, do not apply if building for a method, add to getter/hc/equals/toString. --- src/core/lombok/CheckReturnValue.java | 2 +- src/core/lombok/ConfigurationKeys.java | 20 +++++++++++------ .../configuration/CheckReturnValueFlavor.java | 22 ++++++++++++++++--- .../core/configuration/JacksonVersion.java | 6 +++-- .../eclipse/handlers/EclipseHandlerUtil.java | 20 ++++++++--------- .../eclipse/handlers/HandleBuilder.java | 4 ++-- .../handlers/HandleEqualsAndHashCode.java | 3 +++ .../lombok/eclipse/handlers/HandleGetter.java | 1 + .../lombok/eclipse/handlers/HandleSetter.java | 3 +-- .../eclipse/handlers/HandleSuperBuilder.java | 1 + .../eclipse/handlers/HandleToString.java | 2 ++ .../lombok/eclipse/handlers/HandleWith.java | 2 +- .../lombok/eclipse/handlers/HandleWithBy.java | 2 +- .../lombok/javac/handlers/HandleBuilder.java | 6 ++--- .../handlers/HandleEqualsAndHashCode.java | 2 ++ .../lombok/javac/handlers/HandleGetter.java | 5 ++++- .../javac/handlers/HandleSuperBuilder.java | 3 ++- .../lombok/javac/handlers/HandleToString.java | 1 + .../lombok/javac/handlers/HandleWith.java | 4 ++-- .../javac/handlers/JavacHandlerUtil.java | 14 ++++-------- 20 files changed, 77 insertions(+), 46 deletions(-) diff --git a/src/core/lombok/CheckReturnValue.java b/src/core/lombok/CheckReturnValue.java index 0f6ae868..0736108f 100644 --- a/src/core/lombok/CheckReturnValue.java +++ b/src/core/lombok/CheckReturnValue.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2025 The Project Lombok Authors. + * Copyright (C) 2025-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 diff --git a/src/core/lombok/ConfigurationKeys.java b/src/core/lombok/ConfigurationKeys.java index 9dafc083..6ea02ebe 100644 --- a/src/core/lombok/ConfigurationKeys.java +++ b/src/core/lombok/ConfigurationKeys.java @@ -90,16 +90,22 @@ public class ConfigurationKeys { * If {@code true}, lombok generates {@code @lombok.Generated} on all fields, methods, and types that are generated. */ public static final ConfigurationKey ADD_LOMBOK_GENERATED_ANNOTATIONS = new ConfigurationKey("lombok.addLombokGeneratedAnnotation", "Generate @lombok.Generated on all generated code (default: true).") {}; - + /** - * lombok configuration: {@code lombok.checkReturnValueAnnotation} = [{@code none} | {@code lombok}]. + * lombok configuration: {@code lombok.checkReturnValueAnnotation} += {@code lombok} / {@code androidx}. * - * If set to {@code lombok}, lombok generates {@code @lombok.CheckReturnValue} on generated methods where the return value should not be ignored, - * such as {@code @With} methods and {@code @Builder}'s {@code build()} method. - * If set to {@code none} (the current default), no such annotation is emitted. A future lombok release may flip the default to {@code lombok}. + * If set, lombok generates all requested annotation flavors indicating that a caller must use the returned value on all methods where it is sensible, such as {@code @With} methods and {@code @Builder}'s {@code build()} method. + *

+ * If value {@code lombok} is added, lombok generates {@code @lombok.CheckReturnValue}. + *

+ * If value {@code androidx} is added, lombok generates {@code @androidx.annotation.CheckResult}. + *

+ * The current default is no flavors (no annotations are added), but a future lombok release may flip the default {@code lombok}. + *

+ * NB: {@code build()} methods only get these annotations if the thing they are building for is a type or constructor. Applying {@code @Builder} to a method will not result in these annotations unless that method is itself so annotated. */ - public static final ConfigurationKey CHECK_RETURN_VALUE_ANNOTATION = new ConfigurationKey("lombok.checkReturnValueAnnotation", "Which @CheckReturnValue annotation flavor to emit on generated methods. Values: none, lombok (default: none).") {}; - + public static final ConfigurationKey> CHECK_RETURN_VALUE_ANNOTATION = new ConfigurationKey>("lombok.checkReturnValueAnnotation", "Which @CheckReturnValue annotation flavor(s) to emit on generated methods. Values: lombok, androidx (default: none).") {}; + /** * lombok configuration: {@code lombok.extern.findbugs.addSuppressFBWarnings} = {@code true} | {@code false}. * diff --git a/src/core/lombok/core/configuration/CheckReturnValueFlavor.java b/src/core/lombok/core/configuration/CheckReturnValueFlavor.java index b313ee73..15b1225b 100644 --- a/src/core/lombok/core/configuration/CheckReturnValueFlavor.java +++ b/src/core/lombok/core/configuration/CheckReturnValueFlavor.java @@ -21,7 +21,23 @@ */ package lombok.core.configuration; -/** Used for lombok configuration to select which {@code @CheckReturnValue} annotation flavor to emit on generated methods. */ -public enum CheckReturnValueFlavor { - NONE, LOMBOK; +/** Used for lombok configuration to select which {@code @CheckReturnValue} annotation flavor(s) to emit on generated methods. */ +public enum CheckReturnValueFlavor implements MappedConfigEnum { + LOMBOK, + ANDROIDX, + ; + + @Override public boolean matches(String value) { + return value.equals(this.name().toLowerCase()); + } + + @Override public String toString() { + return this.name().toLowerCase(); + } + + public String getMarkerAnnotationFullyQualifiedName() { + if (this == LOMBOK) return "lombok.CheckReturnValue"; + if (this == ANDROIDX) return "androidx.annotation.CheckResult"; + throw new IllegalStateException("bug"); + } } diff --git a/src/core/lombok/core/configuration/JacksonVersion.java b/src/core/lombok/core/configuration/JacksonVersion.java index 480dd973..89af28ec 100644 --- a/src/core/lombok/core/configuration/JacksonVersion.java +++ b/src/core/lombok/core/configuration/JacksonVersion.java @@ -31,11 +31,13 @@ public enum JacksonVersion implements MappedConfigEnum { @Override public boolean matches(String value) { if (this == TWO) return "2".equals(value); - return "3".equals(value); + if (this == THREE) return "3".equals(value); + throw new IllegalStateException("bug"); } @Override public String toString() { if (this == TWO) return "2"; - return "3"; + if (this == THREE) return "3"; + throw new IllegalStateException("bug"); } } diff --git a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java index a8383898..68763853 100644 --- a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java +++ b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java @@ -2108,7 +2108,7 @@ public class EclipseHandlerUtil { private static final char[][] JAKARTA_ANNOTATION_GENERATED = Eclipse.fromQualifiedName("jakarta.annotation.Generated"); private static final char[][] LOMBOK_GENERATED = Eclipse.fromQualifiedName("lombok.Generated"); private static final char[][] EDU_UMD_CS_FINDBUGS_ANNOTATIONS_SUPPRESSFBWARNINGS = Eclipse.fromQualifiedName("edu.umd.cs.findbugs.annotations.SuppressFBWarnings"); - private static final char[][] LOMBOK_CHECK_RETURN_VALUE = Eclipse.fromQualifiedName("lombok.CheckReturnValue"); + private static final Map checkReturnValueQualifiedNameCache = new HashMap(); public static Annotation[] addSuppressWarningsAll(EclipseNode node, ASTNode source, Annotation[] originalAnnotationArray) { Annotation[] anns = originalAnnotationArray; @@ -2140,17 +2140,17 @@ public class EclipseHandlerUtil { } public static Annotation[] addCheckReturnValue(EclipseNode node, ASTNode source, Annotation[] originalAnnotationArray) { - CheckReturnValueFlavor flavor = node.getAst().readConfiguration(ConfigurationKeys.CHECK_RETURN_VALUE_ANNOTATION); - if (flavor == null) flavor = CheckReturnValueFlavor.NONE; - switch (flavor) { - case LOMBOK: - return addAnnotation(source, originalAnnotationArray, LOMBOK_CHECK_RETURN_VALUE); - case NONE: - default: - return originalAnnotationArray; + Annotation[] out = originalAnnotationArray; + List flavors = node.getAst().readConfiguration(ConfigurationKeys.CHECK_RETURN_VALUE_ANNOTATION); + for (CheckReturnValueFlavor flavor : flavors) { + char[][] fqn = checkReturnValueQualifiedNameCache.get(flavor); + if (fqn == null) checkReturnValueQualifiedNameCache.put(flavor, fqn = Eclipse.fromQualifiedName(flavor.getMarkerAnnotationFullyQualifiedName())); + out = addAnnotation(source, out, fqn); } + + return out; } - + static Annotation[] addAnnotation(ASTNode source, Annotation[] originalAnnotationArray, char[][] annotationTypeFqn) { return addAnnotation(source, originalAnnotationArray, annotationTypeFqn, (ASTNode[]) null); } diff --git a/src/core/lombok/eclipse/handlers/HandleBuilder.java b/src/core/lombok/eclipse/handlers/HandleBuilder.java index 750a6b21..51aa2f21 100755 --- a/src/core/lombok/eclipse/handlers/HandleBuilder.java +++ b/src/core/lombok/eclipse/handlers/HandleBuilder.java @@ -897,12 +897,12 @@ public class HandleBuilder extends EclipseAnnotationHandler { out.annotations = new Annotation[] {generateNamedAnnotation(job.source, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE)}; } out.receiver = generateBuildReceiver(job); - out.annotations = addCheckReturnValue(job.builderType, job.source, out.annotations); + if (job.checkReturnValue) out.annotations = addCheckReturnValue(job.builderType, job.source, out.annotations); if (staticName == null) createRelevantNonNullAnnotation(job.builderType, out); out.traverse(new SetGeneratedByVisitor(job.source), (ClassScope) null); return out; } - + private TypeReference[] typeParameterNames(TypeParameter[] typeParameters) { if (typeParameters == null) return null; diff --git a/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java b/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java index b0e0e526..5ed93c09 100755 --- a/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java +++ b/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java @@ -272,6 +272,8 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler { findDelegatesAndMarkAsHandled(fieldNode), checkerFramework, deprecated); + method.annotations = EclipseHandlerUtil.addCheckReturnValue(fieldNode, method, method.annotations); } if (addSuppressWarningsUnchecked) { diff --git a/src/core/lombok/eclipse/handlers/HandleSetter.java b/src/core/lombok/eclipse/handlers/HandleSetter.java index 662c6d4e..b0730d39 100644 --- a/src/core/lombok/eclipse/handlers/HandleSetter.java +++ b/src/core/lombok/eclipse/handlers/HandleSetter.java @@ -27,7 +27,6 @@ import static lombok.eclipse.handlers.EclipseHandlerUtil.*; import java.lang.reflect.Modifier; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.List; @@ -163,7 +162,7 @@ public class HandleSetter extends EclipseAnnotationHandler { AnnotationValues accessors = getAccessorsForField(fieldNode); String setterName = toSetterName(fieldNode, isBoolean, accessors); boolean shouldReturnThis = shouldReturnThis(fieldNode, accessors); - boolean fluent = accessors.isExplicit("fluent"); + boolean fluent = accessors.isExplicit("fluent"); if (setterName == null) { fieldNode.addWarning("Not generating setter for this field: It does not fit your @Accessors prefix list."); diff --git a/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java b/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java index c145b911..7aeb03a6 100644 --- a/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java +++ b/src/core/lombok/eclipse/handlers/HandleSuperBuilder.java @@ -868,6 +868,7 @@ public class HandleSuperBuilder extends EclipseAnnotationHandler { else if (overrideAnn != null) out.annotations = new Annotation[] {overrideAnn}; else if (sefAnn != null) out.annotations = new Annotation[] {sefAnn}; out.receiver = HandleBuilder.generateBuildReceiver(job); + out.annotations = addCheckReturnValue(job.builderType, job.source, out.annotations); out.traverse(new SetGeneratedByVisitor(job.source), (ClassScope) null); return out; } diff --git a/src/core/lombok/eclipse/handlers/HandleToString.java b/src/core/lombok/eclipse/handlers/HandleToString.java index b22d162f..72043264 100644 --- a/src/core/lombok/eclipse/handlers/HandleToString.java +++ b/src/core/lombok/eclipse/handlers/HandleToString.java @@ -298,6 +298,8 @@ public class HandleToString extends EclipseAnnotationHandler { } else { method.annotations = new Annotation[] { overrideAnnotation }; } + method.annotations = EclipseHandlerUtil.addCheckReturnValue(type, method, method.annotations); + method.arguments = null; method.selector = "toString".toCharArray(); method.thrownExceptions = null; diff --git a/src/core/lombok/eclipse/handlers/HandleWith.java b/src/core/lombok/eclipse/handlers/HandleWith.java index b286e769..9a657b2c 100644 --- a/src/core/lombok/eclipse/handlers/HandleWith.java +++ b/src/core/lombok/eclipse/handlers/HandleWith.java @@ -293,7 +293,7 @@ public class HandleWith extends EclipseAnnotationHandler { EclipseHandlerUtil.createRelevantNonNullAnnotation(fieldNode, method); method.annotations = EclipseHandlerUtil.addCheckReturnValue(fieldNode, source, method.annotations); - + method.traverse(new SetGeneratedByVisitor(source), parent.scope); copyJavadoc(fieldNode, method, CopyJavadoc.WITH); return method; diff --git a/src/core/lombok/eclipse/handlers/HandleWithBy.java b/src/core/lombok/eclipse/handlers/HandleWithBy.java index e25bb843..27617827 100644 --- a/src/core/lombok/eclipse/handlers/HandleWithBy.java +++ b/src/core/lombok/eclipse/handlers/HandleWithBy.java @@ -376,7 +376,7 @@ public class HandleWithBy extends EclipseAnnotationHandler { createRelevantNonNullAnnotation(sourceNode, param, method); createRelevantNonNullAnnotation(fieldNode, method); method.annotations = addCheckReturnValue(fieldNode, source, method.annotations); - + method.traverse(new SetGeneratedByVisitor(source), parent.scope); copyJavadoc(fieldNode, method, CopyJavadoc.WITH_BY); return method; diff --git a/src/core/lombok/javac/handlers/HandleBuilder.java b/src/core/lombok/javac/handlers/HandleBuilder.java index 2b944f1d..595695cd 100644 --- a/src/core/lombok/javac/handlers/HandleBuilder.java +++ b/src/core/lombok/javac/handlers/HandleBuilder.java @@ -347,7 +347,7 @@ public class HandleBuilder extends JavacAnnotationHandler { } if (job.builderClassName.indexOf('*') > -1) { String replStr = returnTypeToBuilderClassName(annotationNode, td, buildMethodReturnType, job.typeParams); - if (replStr == null) return; // shuold not happen + if (replStr == null) return; // should not happen job.builderClassName = job.builderClassName.replace("*", replStr); } if (job.toBuilder) { @@ -810,11 +810,11 @@ public class HandleBuilder extends JavacAnnotationHandler { } else { methodDef = maker.MethodDef(maker.Modifiers(toJavacModifier(job.accessInners), annsOnMethod), job.toName(job.buildMethodName), returnTypeCopy, List.nil(), List.nil(), thrownExceptions, body, null); } - addCheckReturnValue(methodDef.mods, job.builderType, job.sourceNode); + if (job.checkReturnValue) addCheckReturnValue(methodDef.mods, job.builderType, job.sourceNode); if (staticName == null) createRelevantNonNullAnnotation(job.builderType, methodDef); return methodDef; } - + public static JCMethodDecl generateDefaultProvider(Name methodName, JavacNode fieldNode, List params, BuilderJob job) { JavacTreeMaker maker = fieldNode.getTreeMaker(); JCVariableDecl field = (JCVariableDecl) fieldNode.get(); diff --git a/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java b/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java index 07610a0b..50818201 100644 --- a/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java +++ b/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java @@ -228,6 +228,7 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandlernil())); } JCModifiers mods = maker.Modifiers(Flags.PUBLIC, annsOnMethod); + addCheckReturnValue(mods, typeNode, source); JCExpression returnType = maker.TypeIdent(CTC_INT); ListBuffer statements = new ListBuffer(); @@ -439,6 +440,7 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandlernil())); } JCModifiers mods = maker.Modifiers(Flags.PUBLIC, annsOnMethod); + addCheckReturnValue(mods, typeNode, source); JCExpression objectType; if (annsOnParamOnMethod.isEmpty()) { objectType = genJavaLangTypeRef(typeNode, "Object"); diff --git a/src/core/lombok/javac/handlers/HandleGetter.java b/src/core/lombok/javac/handlers/HandleGetter.java index f6b28ffb..ce29cb3c 100644 --- a/src/core/lombok/javac/handlers/HandleGetter.java +++ b/src/core/lombok/javac/handlers/HandleGetter.java @@ -56,6 +56,7 @@ import com.sun.tools.javac.tree.JCTree.JCExpressionStatement; import com.sun.tools.javac.tree.JCTree.JCIf; import com.sun.tools.javac.tree.JCTree.JCMethodDecl; import com.sun.tools.javac.tree.JCTree.JCMethodInvocation; +import com.sun.tools.javac.tree.JCTree.JCModifiers; import com.sun.tools.javac.tree.JCTree.JCPrimitiveTypeTree; import com.sun.tools.javac.tree.JCTree.JCStatement; import com.sun.tools.javac.tree.JCTree.JCSynchronized; @@ -264,9 +265,11 @@ public class HandleGetter extends JavacAnnotationHandler { if (getCheckerFrameworkVersion(field).generateSideEffectFree()) annsOnMethod = annsOnMethod.prepend(treeMaker.Annotation(genTypeRef(field, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE), List.nil())); } if (isFieldDeprecated(field)) annsOnMethod = annsOnMethod.prepend(treeMaker.Annotation(genJavaLangTypeRef(field, "Deprecated"), List.nil())); + JCModifiers mods = treeMaker.Modifiers(access, annsOnMethod); + addCheckReturnValue(mods, field, source); if (makeFinal) access |= Flags.FINAL; - JCMethodDecl decl = recursiveSetGeneratedBy(treeMaker.MethodDef(treeMaker.Modifiers(access, annsOnMethod), methodName, methodType, + JCMethodDecl decl = recursiveSetGeneratedBy(treeMaker.MethodDef(mods, methodName, methodType, methodGenericParams, parameters, throwsClauses, methodBody, annotationMethodDefaultValue), source); decl.mods.annotations = decl.mods.annotations.appendList(delegates); diff --git a/src/core/lombok/javac/handlers/HandleSuperBuilder.java b/src/core/lombok/javac/handlers/HandleSuperBuilder.java index a48225fa..fa0ba9dd 100644 --- a/src/core/lombok/javac/handlers/HandleSuperBuilder.java +++ b/src/core/lombok/javac/handlers/HandleSuperBuilder.java @@ -841,6 +841,7 @@ public class HandleSuperBuilder extends JavacAnnotationHandler { } else { methodDef = maker.MethodDef(modifiers, name, returnType, List.nil(), List.nil(), List.nil(), null, null); } + addCheckReturnValue(methodDef.mods, job.builderType, job.sourceNode); return methodDef; } @@ -873,7 +874,7 @@ public class HandleSuperBuilder extends JavacAnnotationHandler { createRelevantNonNullAnnotation(job.builderType, methodDef); return methodDef; } - + private JCMethodDecl generateCleanMethod(java.util.List builderFields, JavacNode type, JavacNode source) { JavacTreeMaker maker = type.getTreeMaker(); ListBuffer statements = new ListBuffer(); diff --git a/src/core/lombok/javac/handlers/HandleToString.java b/src/core/lombok/javac/handlers/HandleToString.java index 8eccf079..4737e9ac 100644 --- a/src/core/lombok/javac/handlers/HandleToString.java +++ b/src/core/lombok/javac/handlers/HandleToString.java @@ -153,6 +153,7 @@ public class HandleToString extends JavacAnnotationHandler { List annsOnMethod = List.of(overrideAnnotation); if (getCheckerFrameworkVersion(typeNode).generateSideEffectFree()) annsOnMethod = annsOnMethod.prepend(maker.Annotation(genTypeRef(typeNode, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE), List.nil())); JCModifiers mods = maker.Modifiers(Flags.PUBLIC, annsOnMethod); + addCheckReturnValue(mods, typeNode, source); JCExpression returnType = genJavaLangTypeRef(typeNode, "String"); boolean first = true; diff --git a/src/core/lombok/javac/handlers/HandleWith.java b/src/core/lombok/javac/handlers/HandleWith.java index 395cae65..bd80b351 100644 --- a/src/core/lombok/javac/handlers/HandleWith.java +++ b/src/core/lombok/javac/handlers/HandleWith.java @@ -281,9 +281,9 @@ public class HandleWith extends JavacAnnotationHandler { List annsOnMethod = copyAnnotations(onMethod, maker); CheckerFrameworkVersion checkerFramework = getCheckerFrameworkVersion(source); if (checkerFramework.generateSideEffectFree()) annsOnMethod = annsOnMethod.prepend(maker.Annotation(genTypeRef(source, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE), List.nil())); - + if (isFieldDeprecated(field)) annsOnMethod = annsOnMethod.prepend(maker.Annotation(genJavaLangTypeRef(field, "Deprecated"), List.nil())); - + if (makeAbstract) access |= Flags.ABSTRACT; AnnotationValues accessors = JavacHandlerUtil.getAccessorsForField(field); boolean makeFinal = shouldMakeFinal(field, accessors); diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java index dc459d6e..f2ac8e4d 100644 --- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java +++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java @@ -1556,18 +1556,12 @@ public class JavacHandlerUtil { } public static void addCheckReturnValue(JCModifiers mods, JavacNode node, JavacNode source) { - CheckReturnValueFlavor flavor = node.getAst().readConfiguration(ConfigurationKeys.CHECK_RETURN_VALUE_ANNOTATION); - if (flavor == null) flavor = CheckReturnValueFlavor.NONE; - switch (flavor) { - case LOMBOK: - addAnnotation(mods, node, source, "lombok.CheckReturnValue", null); - return; - case NONE: - default: - return; + java.util.List flavors = node.getAst().readConfiguration(ConfigurationKeys.CHECK_RETURN_VALUE_ANNOTATION); + for (CheckReturnValueFlavor flavor : flavors) { + addAnnotation(mods, node, source, flavor.getMarkerAnnotationFullyQualifiedName(), null); } } - + public static void addAnnotation(JCModifiers mods, JavacNode node, JavacNode source, String annotationTypeFqn, JCExpression arg) { boolean isJavaLangBased; String simpleName; { From 02c68c112df20ec7b3f2cdc492606237c68e3826 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Fri, 21 Aug 2026 02:38:25 +0200 Subject: [PATCH 7/9] [docs][config: `lombok.CheckReturnValue`] --- website/templates/features/Builder.html | 2 ++ website/templates/features/SuperBuilder.html | 2 ++ website/templates/features/configuration.html | 7 +++++-- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/website/templates/features/Builder.html b/website/templates/features/Builder.html index 4fd90741..0449620f 100644 --- a/website/templates/features/Builder.html +++ b/website/templates/features/Builder.html @@ -234,6 +234,8 @@ public class JacksonExample { protected keyword is different inside the inner class, and the precise behavior that PROTECTED would indicate (access by any source in the same package is allowed, as well as any subclasses from the outer class, marked with @Builder is not possible, and marking the inner members public is as close as we can get.

If you have configured a nullity annotation flavour via lombok.config key lombok.addNullAnnotations, any plural-form generated builder methods for @Singular marked properties (these plural form methods take a collection of some sort and add all elements) get a nullity annotation on the parameter. You get a non-null one normally, but if you have configured the behavior on null being passed in as collection to IGNORE, a nullable annotation is generated instead. +

+ The generated build() method will be annotated to indicate that callers must not ignore the returned value if you ask lombok to do this via the lombok.checkReturnValueAnnotation configuration key. However, if applying @Builder to a method, lombok doesn't add such an annotation, unless the method you marked with @Builder is itself marked with any annotation that implies 'check return value', including annotations that indicate side effect free or pure. If @Builder is applied to an explicitly written constructor, the @CheckReturnValue annotation will be added to the build() method if requested, even though constructors can in theory have side effects, and thus, ignoring the return value could be acceptable. Nevertheless, lombok assumes all constructors have no effect other than making an object, and you cannot override this assumption.

diff --git a/website/templates/features/SuperBuilder.html b/website/templates/features/SuperBuilder.html index bc682468..0af65b85 100644 --- a/website/templates/features/SuperBuilder.html +++ b/website/templates/features/SuperBuilder.html @@ -86,6 +86,8 @@ For remarks on @Singular, see the @Builder documentation's small print.

Various well known annotations about nullity cause null checks to be inserted and will be copied to parameter of the builder's 'setter' method. See Getter/Setter documentation's small print for more information. +

+ The generated build() method will be annotated to indicate that callers must not ignore the returned value if you ask lombok to do this via the lombok.checkReturnValueAnnotation configuration key. While in theory constructors can have side effects, lombok always assumes they do not, and you cannot override this assumption.

diff --git a/website/templates/features/configuration.html b/website/templates/features/configuration.html index a09b0c81..fc96e0f0 100644 --- a/website/templates/features/configuration.html +++ b/website/templates/features/configuration.html @@ -90,11 +90,14 @@ Many flavors are available: jspecify (recommended), checkerframework (recommended), jakarta, eclipse, jetbrains, netbeans, androidx, findbugs, spring, jml, javax (=JSR305; not recommended), android.support (deprecated within android), or define your own via CUSTOM:fully.qualified.NonNullAnnotation:fully.qualified.NullableAnnotation; if your nullity annotation is solely of the type use style (it annotates types, such as eclipse's and checkerframework's offerings, versus annotating methods and parameters), the format is CUSTOM:TYPE_USE:nonnullanno:nullableanno.
This feature was added in lombok v1.18.12.

- Lombok can add a @CheckReturnValue annotation to generated methods whose return value should not be ignored, such as withX methods and @Builder's build() method. Static analysis tools (IntelliJ, Error Prone, SpotBugs) will then warn when callers discard the result. By default, no such annotation is added. Enable with: + Lombok can add annotations to methods it generates if code that invokes this method should always to something with the returned value. For example, withX methods, and @Builder's build() method.
+ Lombok ships with its own take on this annotation named @lombok.CheckReturnValue. Static analysis tools (IntelliJ, Error Prone, SpotBugs) will then warn when callers discard the result. By default, no such annotation is added. Enable with:

  1. lombok.checkReturnValueAnnotation = <flavor>
- Currently the only non-none flavor is lombok, which emits @lombok.CheckReturnValue. A future lombok release may flip the default to lombok.
+ Supported flavors are: lombok, as well as androidx which emits @androidx.annotation.CheckResult.
+ Default: none. A future lombok release may flip the default to lombok.
+ This feature was added in lombok v1.18.48.

Lombok can be configured to add @lombok.Generated annotations to all generated nodes where possible; useful for JaCoCo (which has built in support), or other style checkers and code coverage tools: From f6fab0dafb3b82739d7f0603e30789ae1cc2ee59 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Fri, 21 Aug 2026 02:41:09 +0200 Subject: [PATCH 8/9] [trivial] Add some type param hints so older javacs can compile it. --- src/core/lombok/javac/handlers/HandleConstructor.java | 4 ++-- src/core/lombok/javac/handlers/HandleStandardException.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/lombok/javac/handlers/HandleConstructor.java b/src/core/lombok/javac/handlers/HandleConstructor.java index 05571165..046d51ad 100644 --- a/src/core/lombok/javac/handlers/HandleConstructor.java +++ b/src/core/lombok/javac/handlers/HandleConstructor.java @@ -363,7 +363,7 @@ public class HandleConstructor { CheckerFrameworkVersion checkerFramework = getCheckerFrameworkVersion(source); List anns = checkerFramework.generateSideEffectFree() ? List.of(maker.Annotation(genTypeRef(source, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE), List.nil())) : - List.nil(); + List.nil(); JCModifiers mods = maker.Modifiers(toJavacModifier(level), anns); if (addConstructorProperties && !isLocalType(typeNode) && LombokOptionsFactory.getDelombokOptions(typeNode.getContext()).getFormatPreferences().generateConstructorProperties()) { @@ -479,7 +479,7 @@ public class HandleConstructor { CheckerFrameworkVersion checkerFramework = getCheckerFrameworkVersion(source); List anns = checkerFramework.generateSideEffectFree() ? List.of(maker.Annotation(genTypeRef(source, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE), List.nil())) : - List.nil(); + List.nil(); JCModifiers mods = maker.Modifiers(Flags.STATIC | toJavacModifier(level), anns); diff --git a/src/core/lombok/javac/handlers/HandleStandardException.java b/src/core/lombok/javac/handlers/HandleStandardException.java index 4b4df8e9..8b28c52e 100644 --- a/src/core/lombok/javac/handlers/HandleStandardException.java +++ b/src/core/lombok/javac/handlers/HandleStandardException.java @@ -209,7 +209,7 @@ public class HandleStandardException extends JavacAnnotationHandler anns = checkerFramework.generateSideEffectFree() ? List.of(maker.Annotation(genTypeRef(source, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE), List.nil())) : - List.nil(); + List.nil(); JCModifiers mods = maker.Modifiers(toJavacModifier(level), anns); if (addConstructorProperties) addConstructorProperties(mods, typeNode, msgParam, causeParam); From 4cfdf214300869ccdc893bb3bcd91910b02300e7 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Fri, 21 Aug 2026 02:57:22 +0200 Subject: [PATCH 9/9] [test] Fix tests to reflect updates to `@CheckReturnValue` feature. --- .../after-delombok/CheckReturnValueBuilder.java | 1 + .../after-delombok/CheckReturnValueOff.java | 14 -------------- .../after-delombok/CheckerFrameworkBasic.java | 1 + .../after-delombok/CheckerFrameworkBuilder.java | 2 ++ .../CheckerFrameworkSuperBuilder.java | 2 ++ .../after-ecj/CheckReturnValueBuilder.java | 2 +- .../resource/after-ecj/CheckReturnValueOff.java | 14 -------------- .../resource/after-ecj/CheckerFrameworkBasic.java | 2 +- .../after-ecj/CheckerFrameworkBuilder.java | 4 ++-- .../resource/before/CheckReturnValueOff.java | 9 --------- 10 files changed, 10 insertions(+), 41 deletions(-) delete mode 100644 test/transform/resource/after-delombok/CheckReturnValueOff.java delete mode 100644 test/transform/resource/after-ecj/CheckReturnValueOff.java delete mode 100644 test/transform/resource/before/CheckReturnValueOff.java diff --git a/test/transform/resource/after-delombok/CheckReturnValueBuilder.java b/test/transform/resource/after-delombok/CheckReturnValueBuilder.java index c8d84291..f8288793 100644 --- a/test/transform/resource/after-delombok/CheckReturnValueBuilder.java +++ b/test/transform/resource/after-delombok/CheckReturnValueBuilder.java @@ -45,6 +45,7 @@ class CheckReturnValueBuilder { return new CheckReturnValueBuilder(this.x, this.name); } @java.lang.Override + @lombok.CheckReturnValue @java.lang.SuppressWarnings("all") @lombok.Generated public java.lang.String toString() { diff --git a/test/transform/resource/after-delombok/CheckReturnValueOff.java b/test/transform/resource/after-delombok/CheckReturnValueOff.java deleted file mode 100644 index ecffc92e..00000000 --- a/test/transform/resource/after-delombok/CheckReturnValueOff.java +++ /dev/null @@ -1,14 +0,0 @@ -class CheckReturnValueOff { - final int x; - CheckReturnValueOff(int x) { - this.x = x; - } - /** - * @return a clone of this object, except with this updated property (returns {@code this} if an identical value is passed). - */ - @java.lang.SuppressWarnings("all") - @lombok.Generated - public CheckReturnValueOff withX(final int x) { - return this.x == x ? this : new CheckReturnValueOff(x); - } -} diff --git a/test/transform/resource/after-delombok/CheckerFrameworkBasic.java b/test/transform/resource/after-delombok/CheckerFrameworkBasic.java index 62c8b0c7..dd59b9eb 100644 --- a/test/transform/resource/after-delombok/CheckerFrameworkBasic.java +++ b/test/transform/resource/after-delombok/CheckerFrameworkBasic.java @@ -70,6 +70,7 @@ class CheckerFrameworkBasic { public java.lang.String toString() { return "CheckerFrameworkBasic(x=" + this.getX() + ", y=" + this.getY() + ", z=" + this.getZ() + ")"; } + @org.checkerframework.dataflow.qual.SideEffectFree @java.lang.SuppressWarnings("all") @lombok.Generated public CheckerFrameworkBasic(final int x, final int y, final int z) { diff --git a/test/transform/resource/after-delombok/CheckerFrameworkBuilder.java b/test/transform/resource/after-delombok/CheckerFrameworkBuilder.java index 5c31e346..c19bf5cf 100644 --- a/test/transform/resource/after-delombok/CheckerFrameworkBuilder.java +++ b/test/transform/resource/after-delombok/CheckerFrameworkBuilder.java @@ -11,6 +11,7 @@ class CheckerFrameworkBuilder { private static int $default$x() { return 5; } + @org.checkerframework.dataflow.qual.SideEffectFree @java.lang.SuppressWarnings("all") @lombok.Generated CheckerFrameworkBuilder(final int x, final int y, final int z, final List names) { @@ -37,6 +38,7 @@ class CheckerFrameworkBuilder { @java.lang.SuppressWarnings("all") @lombok.Generated private java.util.ArrayList names; + @org.checkerframework.dataflow.qual.SideEffectFree @java.lang.SuppressWarnings("all") @lombok.Generated CheckerFrameworkBuilderBuilder() { diff --git a/test/transform/resource/after-delombok/CheckerFrameworkSuperBuilder.java b/test/transform/resource/after-delombok/CheckerFrameworkSuperBuilder.java index 000b2816..8e62a8b7 100644 --- a/test/transform/resource/after-delombok/CheckerFrameworkSuperBuilder.java +++ b/test/transform/resource/after-delombok/CheckerFrameworkSuperBuilder.java @@ -100,6 +100,7 @@ class CheckerFrameworkSuperBuilder { @java.lang.SuppressWarnings("all") @lombok.Generated private static final class ParentBuilderImpl extends CheckerFrameworkSuperBuilder.Parent.ParentBuilder { + @org.checkerframework.dataflow.qual.SideEffectFree @java.lang.SuppressWarnings("all") @lombok.Generated private ParentBuilderImpl() { @@ -207,6 +208,7 @@ class CheckerFrameworkSuperBuilder { @java.lang.SuppressWarnings("all") @lombok.Generated private static final class ZChildBuilderImpl extends CheckerFrameworkSuperBuilder.ZChild.ZChildBuilder { + @org.checkerframework.dataflow.qual.SideEffectFree @java.lang.SuppressWarnings("all") @lombok.Generated private ZChildBuilderImpl() { diff --git a/test/transform/resource/after-ecj/CheckReturnValueBuilder.java b/test/transform/resource/after-ecj/CheckReturnValueBuilder.java index d7da093d..fa85fea1 100644 --- a/test/transform/resource/after-ecj/CheckReturnValueBuilder.java +++ b/test/transform/resource/after-ecj/CheckReturnValueBuilder.java @@ -22,7 +22,7 @@ public @lombok.CheckReturnValue @java.lang.SuppressWarnings("all") @lombok.Generated CheckReturnValueBuilder build() { return new CheckReturnValueBuilder(this.x, this.name); } - public @java.lang.Override @java.lang.SuppressWarnings("all") @lombok.Generated java.lang.String toString() { + public @java.lang.Override @lombok.CheckReturnValue @java.lang.SuppressWarnings("all") @lombok.Generated java.lang.String toString() { return (((("CheckReturnValueBuilder.CheckReturnValueBuilderBuilder(x=" + this.x) + ", name=") + this.name) + ")"); } } diff --git a/test/transform/resource/after-ecj/CheckReturnValueOff.java b/test/transform/resource/after-ecj/CheckReturnValueOff.java deleted file mode 100644 index b786cdbc..00000000 --- a/test/transform/resource/after-ecj/CheckReturnValueOff.java +++ /dev/null @@ -1,14 +0,0 @@ -import lombok.With; -class CheckReturnValueOff { - final @With int x; - CheckReturnValueOff(int x) { - super(); - this.x = x; - } - /** - * @return a clone of this object, except with this updated property (returns {@code this} if an identical value is passed). - */ - public @java.lang.SuppressWarnings("all") @lombok.Generated CheckReturnValueOff withX(final int x) { - return ((this.x == x) ? this : new CheckReturnValueOff(x)); - } -} diff --git a/test/transform/resource/after-ecj/CheckerFrameworkBasic.java b/test/transform/resource/after-ecj/CheckerFrameworkBasic.java index a52e0c2c..8687d8f2 100644 --- a/test/transform/resource/after-ecj/CheckerFrameworkBasic.java +++ b/test/transform/resource/after-ecj/CheckerFrameworkBasic.java @@ -58,7 +58,7 @@ import lombok.With; public @java.lang.Override @org.checkerframework.dataflow.qual.SideEffectFree @java.lang.SuppressWarnings("all") @lombok.Generated java.lang.String toString() { return (((((("CheckerFrameworkBasic(x=" + this.getX()) + ", y=") + this.getY()) + ", z=") + this.getZ()) + ")"); } - public @java.lang.SuppressWarnings("all") @lombok.Generated CheckerFrameworkBasic(final int x, final int y, final int z) { + public @org.checkerframework.dataflow.qual.SideEffectFree @java.lang.SuppressWarnings("all") @lombok.Generated CheckerFrameworkBasic(final int x, final int y, final int z) { super(); this.x = x; this.y = y; diff --git a/test/transform/resource/after-ecj/CheckerFrameworkBuilder.java b/test/transform/resource/after-ecj/CheckerFrameworkBuilder.java index 15872a12..1d40f35f 100644 --- a/test/transform/resource/after-ecj/CheckerFrameworkBuilder.java +++ b/test/transform/resource/after-ecj/CheckerFrameworkBuilder.java @@ -8,7 +8,7 @@ import lombok.Singular; private @java.lang.SuppressWarnings("all") @lombok.Generated int y; private @java.lang.SuppressWarnings("all") @lombok.Generated int z; private @java.lang.SuppressWarnings("all") @lombok.Generated java.util.ArrayList names; - @java.lang.SuppressWarnings("all") @lombok.Generated CheckerFrameworkBuilderBuilder() { + @org.checkerframework.dataflow.qual.SideEffectFree @java.lang.SuppressWarnings("all") @lombok.Generated CheckerFrameworkBuilderBuilder() { super(); } /** @@ -82,7 +82,7 @@ import lombok.Singular; private static @java.lang.SuppressWarnings("all") @lombok.Generated int $default$x() { return 5; } - @java.lang.SuppressWarnings("all") @lombok.Generated CheckerFrameworkBuilder(final int x, final int y, final int z, final List names) { + @org.checkerframework.dataflow.qual.SideEffectFree @java.lang.SuppressWarnings("all") @lombok.Generated CheckerFrameworkBuilder(final int x, final int y, final int z, final List names) { super(); this.x = x; this.y = y; diff --git a/test/transform/resource/before/CheckReturnValueOff.java b/test/transform/resource/before/CheckReturnValueOff.java deleted file mode 100644 index 7197fc75..00000000 --- a/test/transform/resource/before/CheckReturnValueOff.java +++ /dev/null @@ -1,9 +0,0 @@ -//CONF: lombok.checkReturnValueAnnotation = none -import lombok.With; -class CheckReturnValueOff { - @With final int x; - - CheckReturnValueOff(int x) { - this.x = x; - } -}