diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/analysis/FieldAnnotationExtractor.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/analysis/FieldAnnotationExtractor.java
index 3d4e1289..7f0fd9ac 100644
--- a/processor/src/main/java/org/javahelpers/simple/builders/processor/analysis/FieldAnnotationExtractor.java
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/analysis/FieldAnnotationExtractor.java
@@ -44,6 +44,9 @@
/** Extractor for field annotations, converting them from Java model elements to DTOs. */
public final class FieldAnnotationExtractor {
+ /** Fully qualified name of {@link Deprecated}. */
+ private static final String DEPRECATED_FQN = "java.lang.Deprecated";
+
/**
* List of predicates that determine which annotations should be skipped (not copied to the
* builder). Each predicate receives the fully qualified annotation name and returns true if the
@@ -60,6 +63,10 @@ public final class FieldAnnotationExtractor {
name -> name.equals("javax.annotation.processing.Generated"),
// Skip compiler-only annotations not relevant for builder parameters
name -> name.equals("java.lang.SuppressWarnings"),
+ // Skip @Deprecated on parameters — deprecation is propagated to the builder method
+ // itself via DeprecationInfoDto, not to the builder method's parameter (which is a
+ // new declaration, not the original deprecated source parameter)
+ name -> name.equals(DEPRECATED_FQN),
// Skip @Valid annotation for cascading validation (jakarta.validation.Valid /
// javax.validation.Valid) - only meaningful on fields or method return types, not on
// builder method parameters where individual values are set
@@ -70,6 +77,39 @@ private FieldAnnotationExtractor() {
// Private constructor to prevent instantiation
}
+ /**
+ * Extracts the {@code @Deprecated} annotation from the given element, preserving its members
+ * (e.g. {@code since} and {@code forRemoval}).
+ *
+ *
Unlike {@link #extractAnnotations(VariableElement, ProcessingContext)}, this method targets
+ * a single annotation and works on any {@link Element} (method, field, parameter, record
+ * component, type), not just {@link VariableElement}s.
+ *
+ * @param element the element to inspect, or {@code null}
+ * @param context processing context
+ * @return an {@link Optional} containing the {@code @Deprecated} annotation DTO, or empty if the
+ * element is not deprecated
+ */
+ public static Optional extractDeprecatedAnnotation(
+ Element element, ProcessingContext context) {
+ if (element == null) {
+ return Optional.empty();
+ }
+ for (AnnotationMirror mirror : element.getAnnotationMirrors()) {
+ Element annotationElement = mirror.getAnnotationType().asElement();
+ if (!(annotationElement instanceof TypeElement annotationType)) {
+ continue;
+ }
+ if (DEPRECATED_FQN.equals(annotationType.getQualifiedName().toString())) {
+ // Extract the @Deprecated annotation directly, WITHOUT applying ANNOTATION_FILTERS.
+ // The filters are for annotations copied to builder parameters; deprecation detection
+ // must always see @Deprecated regardless of whether it is copied to parameters.
+ return extractAnnotationWithoutFiltering(mirror, context);
+ }
+ }
+ return Optional.empty();
+ }
+
/**
* Extracts annotations from a field parameter. Filters out annotations that should not be copied
* to the builder.
@@ -134,6 +174,25 @@ private static Optional extractAnnotation(
return Optional.empty();
}
+ return extractAnnotationWithoutFiltering(mirror, context);
+ }
+
+ /**
+ * Extracts a single annotation from an AnnotationMirror without applying {@link
+ * #shouldSkipAnnotation} filters. Used by {@link #extractDeprecatedAnnotation} which needs to
+ * detect @Deprecated even though @Deprecated is filtered from parameter annotations.
+ *
+ * @param mirror the annotation mirror to process
+ * @param context processing context
+ * @return Optional containing the extracted annotation
+ */
+ private static Optional extractAnnotationWithoutFiltering(
+ AnnotationMirror mirror, ProcessingContext context) {
+ Element annotationElement = mirror.getAnnotationType().asElement();
+ if (!(annotationElement instanceof TypeElement annotationType)) {
+ return Optional.empty();
+ }
+
// Create AnnotationDto
AnnotationDto annotationDto = new AnnotationDto();
@@ -152,7 +211,7 @@ private static Optional extractAnnotation(
annotationDto.addMember(memberName, memberValue);
}
- context.debug(" -> Added annotation: %s", annotationQualifiedName);
+ context.debug(" -> Added annotation: %s", annotationType.getQualifiedName());
return Optional.of(annotationDto);
}
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/analysis/JavaLangAnalyser.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/analysis/JavaLangAnalyser.java
index 1fa3b5c2..cad55271 100644
--- a/processor/src/main/java/org/javahelpers/simple/builders/processor/analysis/JavaLangAnalyser.java
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/analysis/JavaLangAnalyser.java
@@ -33,6 +33,7 @@
import java.util.Optional;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
@@ -48,6 +49,7 @@
public final class JavaLangAnalyser {
private static final String PARAM_TAG = "@param ";
+ private static final String DEPRECATED_TAG = "@deprecated";
private JavaLangAnalyser() {}
@@ -250,7 +252,7 @@ public static boolean isFunctionalInterface(TypeElement typeElement) {
return true;
}
// Only interfaces can be functional interfaces
- if (typeElement.getKind() != javax.lang.model.element.ElementKind.INTERFACE) {
+ if (typeElement.getKind() != ElementKind.INTERFACE) {
return false;
}
// Heuristic: exactly one abstract method declared (ignores inherited ones for simplicity)
@@ -287,6 +289,32 @@ public static String extractParamJavaDoc(String javaDoc, VariableElement paramet
return extractParamText(lines, indexOfParamTag);
}
+ /**
+ * Extracts the text following the {@code @deprecated} Javadoc tag. Continuation lines are
+ * supported until the next Javadoc tag (starting with '@') or an empty line.
+ *
+ * This is used to propagate the migration hint from a deprecated DTO member to the generated
+ * builder members.
+ *
+ * @param javaDoc the full raw Javadoc as returned by {@code Elements.getDocComment(...)} , or
+ * {@code null}
+ * @return the extracted text or {@code null} if no {@code @deprecated} tag is present or the text
+ * is empty
+ */
+ public static String extractDeprecatedJavaDoc(String javaDoc) {
+ if (javaDoc == null) {
+ return null;
+ }
+ String[] lines = javaDoc.split("\r?\n");
+
+ int indexOfDeprecatedTag = findTagLine(lines, DEPRECATED_TAG);
+ if (indexOfDeprecatedTag < 0) {
+ return null;
+ }
+
+ return extractTagText(lines, indexOfDeprecatedTag, DEPRECATED_TAG, false);
+ }
+
/**
* Finds the line index containing @param tag for the given parameter name.
*
@@ -309,6 +337,24 @@ private static int findParamTagLine(String[] lines, String parameterName) {
return -1;
}
+ /**
+ * Finds the line index containing the given Javadoc tag (without a name argument, e.g. {@code
+ * @deprecated}).
+ *
+ * @param lines the javadoc lines
+ * @param tag the tag including the leading '@' (e.g. {@code "@deprecated"})
+ * @return the line index, or -1 if not found
+ */
+ private static int findTagLine(String[] lines, String tag) {
+ for (int i = 0; i < lines.length; i++) {
+ String cleanedLine = cleanJavadocLine(lines[i]);
+ if (cleanedLine.startsWith(tag)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
/**
* Removes leading asterisk and whitespace from a Javadoc line.
*
@@ -324,34 +370,37 @@ private static String cleanJavadocLine(String rawLine) {
}
/**
- * Extracts the parameter documentation text starting from the @param line.
+ * Extracts the documentation text following a Javadoc tag. Continuation lines are appended until
+ * the next tag (starting with '@') or an empty line.
+ *
+ *
When {@code skipName} is {@code true}, the first word after the tag is treated as a name
+ * argument (e.g. the parameter name in {@code @param name the description}) and skipped. When
+ * {@code false}, the full text after the tag is returned (e.g. for {@code @deprecated use {@link
+ * #x()} instead}).
*
* @param lines the javadoc lines
- * @param startIndex the index of the @param line
- * @return the extracted documentation text or null if empty
+ * @param startIndex the index of the tag line
+ * @param tag the tag including the leading '@' (e.g. {@code "@param"}, {@code "@deprecated"})
+ * @param skipName whether to skip the first word after the tag (name argument)
+ * @return the extracted text or {@code null} if empty
*/
- private static String extractParamText(String[] lines, int startIndex) {
- StringBuilder sb = new StringBuilder();
-
- // Extract initial text from the @param line
+ private static String extractTagText(
+ String[] lines, int startIndex, String tag, boolean skipName) {
String firstLine = cleanJavadocLine(lines[startIndex]);
- String rest = firstLine.substring(PARAM_TAG.length()).trim();
- int spaceIndex = rest.indexOf(' ');
- if (spaceIndex >= 0) {
- String initialText = rest.substring(spaceIndex + 1).trim();
- if (!initialText.isEmpty()) {
- sb.append(initialText);
- }
+ String rest = firstLine.substring(tag.length()).trim();
+ String initialText = skipName ? StringUtils.substringAfter(rest, " ").trim() : rest;
+
+ StringBuilder sb = new StringBuilder();
+ if (!initialText.isEmpty()) {
+ sb.append(initialText);
}
// Append continuation lines until next tag or empty line
for (int i = startIndex + 1; i < lines.length; i++) {
String cleanedLine = cleanJavadocLine(lines[i]);
-
if (cleanedLine.isEmpty() || cleanedLine.startsWith("@")) {
break;
}
-
if (!sb.isEmpty()) {
sb.append(' ');
}
@@ -362,6 +411,19 @@ private static String extractParamText(String[] lines, int startIndex) {
return result.isEmpty() ? null : result;
}
+ /**
+ * Extracts the parameter documentation text starting from the @param line. Delegates to {@link
+ * #extractTagText(String[], int, String, boolean)} with {@code skipName=true} to skip the
+ * parameter name.
+ *
+ * @param lines the javadoc lines
+ * @param startIndex the index of the @param line
+ * @return the extracted documentation text or null if empty
+ */
+ private static String extractParamText(String[] lines, int startIndex) {
+ return extractTagText(lines, startIndex, PARAM_TAG, true);
+ }
+
/**
* Finds the getter method for a given field on the specified DTO type.
*
@@ -397,6 +459,70 @@ public static Optional findGetterForField(
return Optional.empty();
}
+ /**
+ * Finds a field element by name in the given class element.
+ *
+ * @param classElement the class to search in
+ * @param fieldName the simple field name to look for
+ * @return an {@link Optional} containing the field element, or empty if not found
+ */
+ public static Optional findFieldElement(
+ TypeElement classElement, String fieldName) {
+ if (classElement == null || fieldName == null) {
+ return Optional.empty();
+ }
+ return ElementFilter.fieldsIn(classElement.getEnclosedElements()).stream()
+ .filter(e -> e.getSimpleName().contentEquals(fieldName))
+ .findFirst();
+ }
+
+ /**
+ * Finds a record component by name in the given type element.
+ *
+ * @param typeElement the type element to search in
+ * @param componentName the simple component name to look for
+ * @return an {@link Optional} containing the record component element, or empty if not found or
+ * the type is not a record
+ */
+ public static Optional findRecordComponent(
+ TypeElement typeElement, String componentName) {
+ if (typeElement == null || componentName == null) {
+ return Optional.empty();
+ }
+ return typeElement.getRecordComponents().stream()
+ .filter(rc -> rc.getSimpleName().contentEquals(componentName))
+ .map(rc -> (javax.lang.model.element.RecordComponentElement) rc)
+ .findFirst();
+ }
+
+ /**
+ * Finds the setter method for a given field name on the specified DTO type.
+ *
+ * The setter must follow JavaBean conventions ({@code setXxx} with exactly one parameter) and
+ * have a {@code void} return type.
+ *
+ * @param dtoType the enclosing DTO type element
+ * @param fieldName the field name (uncapitalized)
+ * @param context processing context
+ * @return Optional containing the setter ExecutableElement if found
+ */
+ public static Optional findSetterForField(
+ TypeElement dtoType, String fieldName, ProcessingContext context) {
+ if (dtoType == null || fieldName == null) {
+ return Optional.empty();
+ }
+ String setterName = "set" + StringUtils.capitalize(fieldName);
+ List methods = ElementFilter.methodsIn(context.getAllMembers(dtoType));
+ for (ExecutableElement candidate : methods) {
+ if (candidate.getSimpleName().contentEquals(setterName)
+ && candidate.getParameters().size() == 1
+ && candidate.getReturnType().getKind() == VOID) {
+ return Optional.of(candidate);
+ }
+ }
+ return Optional.empty();
+ }
+
/**
* Determines which constructor to use for builder initialization. Prioritizes constructors
* annotated with {@link SimpleBuilderConstructor}. If none is annotated, selects the constructor
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/classgen/roaster/RoasterCodeGenerator.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/classgen/roaster/RoasterCodeGenerator.java
index df6cc2f0..5e3f5df1 100644
--- a/processor/src/main/java/org/javahelpers/simple/builders/processor/classgen/roaster/RoasterCodeGenerator.java
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/classgen/roaster/RoasterCodeGenerator.java
@@ -184,7 +184,13 @@ private void addClassMetadata(JavaClassSource source, GenerationTargetClassDto c
private String renderClassSource(JavaClassSource source) {
String rendered = source.toUnformattedString();
- return formatSource(rendered);
+ String formatted = formatSource(rendered);
+ // Roaster renders some java.lang annotations (e.g. @SuppressWarnings, @Deprecated with
+ // members) with their FQN (@java.lang.SuppressWarnings) even though java.lang types don't
+ // need qualification. Fix this by replacing @java.lang.Xxx with @Xxx for known annotations.
+ formatted = formatted.replace("@java.lang.SuppressWarnings", "@SuppressWarnings");
+ formatted = formatted.replace("@java.lang.Deprecated", "@Deprecated");
+ return formatted;
}
private void appendFields(JavaClassSource source, GenerationTargetClassDto classDef) {
@@ -224,6 +230,7 @@ private void appendConstructor(JavaClassSource source, ConstructorDto constructo
method.addParameter(mapType(param.getParameterType()), param.getParameterName());
}
applyJavadoc(method, constructor.getJavadoc());
+ applyAnnotations(method, constructor.getAnnotations());
applyCodeBlock(method, constructor.getMethodCodeDto());
}
@@ -454,8 +461,14 @@ private void applyAnnotations(
return;
}
for (AnnotationDto annotationDto : annotations) {
- AnnotationSource> annotation =
- source.addAnnotation(annotationDto.getAnnotationType().getFullQualifiedName());
+ TypeName type = annotationDto.getAnnotationType();
+ // Use the simple name for java.lang annotations (e.g. @SuppressWarnings, @Deprecated) so
+ // Roaster renders them without the java.lang prefix. Other annotations use their FQN.
+ String annotationName =
+ "java.lang".equals(type.getPackageName())
+ ? type.getClassName()
+ : type.getFullQualifiedName();
+ AnnotationSource> annotation = source.addAnnotation(annotationName);
for (Map.Entry member : annotationDto.getMembers().entrySet()) {
if ("value".equals(member.getKey())) {
annotation.setLiteralValue(member.getValue());
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/DeprecationInfoDto.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/DeprecationInfoDto.java
new file mode 100644
index 00000000..2ca3e10e
--- /dev/null
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/DeprecationInfoDto.java
@@ -0,0 +1,93 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2026 Andreas Igel
+ *
+ * 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 org.javahelpers.simple.builders.processor.model.core;
+
+import java.util.Optional;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import org.javahelpers.simple.builders.processor.model.annotation.AnnotationDto;
+
+/**
+ * Encapsulates deprecation metadata detected from source DTO property elements.
+ *
+ * This DTO is used during builder definition generation to carry deprecation information from
+ * source elements (constructor parameter, record component, backing field, setter method) to the
+ * point where generated builder methods are annotated. It is an analysis-side DTO and is not
+ * directly used for class generation — the actual rendering uses the existing {@link
+ * org.javahelpers.simple.builders.processor.model.method.BuilderMethodDto#getAnnotations()
+ * annotations list} and {@link org.javahelpers.simple.builders.processor.model.javadoc.JavadocDto
+ * javadoc} on the rendering DTOs.
+ *
+ *
The {@link #deprecatedAnnotation} preserves {@code @Deprecated(since = ..., forRemoval = ...)}
+ * attributes. The {@link #deprecatedJavaDoc} carries the {@code @deprecated} Javadoc tag text.
+ *
+ *
Note: getter method deprecation is not stored here. A deprecated getter means "don't call this
+ * accessor directly anymore", but the builder does not expose a get-API — only the from-instance
+ * constructor calls the getter internally, which is covered by the class-level
+ * {@code @SuppressWarnings}. The setter, however, IS a propagation source because the builder's
+ * fluent methods replace the setter as the write API for the property.
+ *
+ * @param deprecatedAnnotation the {@code @Deprecated} annotation DTO preserving {@code since} and
+ * {@code forRemoval} attributes, or {@code null} if not deprecated
+ * @param deprecatedJavaDoc the {@code @deprecated} Javadoc text, or {@code null} if no explicit tag
+ * is present
+ */
+public record DeprecationInfoDto(AnnotationDto deprecatedAnnotation, String deprecatedJavaDoc) {
+
+ /**
+ * Returns whether this field is deprecated.
+ *
+ * @return {@code true} if a {@code @Deprecated} annotation is present
+ */
+ public boolean isDeprecated() {
+ return deprecatedAnnotation != null;
+ }
+
+ /**
+ * Returns the {@code @Deprecated} annotation DTO as an {@link Optional}.
+ *
+ * @return the annotation DTO, or empty if not deprecated
+ */
+ public Optional getDeprecatedAnnotation() {
+ return Optional.ofNullable(deprecatedAnnotation);
+ }
+
+ /**
+ * Returns the {@code @deprecated} Javadoc text as an {@link Optional}.
+ *
+ * @return the deprecated Javadoc text, or empty if none present
+ */
+ public Optional getDeprecatedJavaDoc() {
+ return Optional.ofNullable(deprecatedJavaDoc);
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
+ .append("deprecatedAnnotation", deprecatedAnnotation)
+ .append("deprecatedJavaDoc", deprecatedJavaDoc)
+ .toString();
+ }
+}
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/FieldDto.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/FieldDto.java
index 463956d3..4f00ce70 100644
--- a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/FieldDto.java
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/FieldDto.java
@@ -66,10 +66,11 @@ public class FieldDto {
private final List fieldGenerics = new ArrayList<>();
/**
- * The name of the getter method to call, because in case of boolean it does not need to start
- * with get (e.g., getName or isActive).
+ * Metadata about the getter method resolved for this field (name and deprecation status). The
+ * from-instance constructor calls the getter to initialise the builder. {@code null} when no
+ * getter was found.
*/
- private String getterName;
+ private GetterInfoDto getterInfo;
/**
* Whether this field is marked as non-nullable via annotations like @NotNull, @NonNull, etc. For
@@ -91,6 +92,12 @@ public class FieldDto {
*/
private String defaultValue;
+ /**
+ * Deprecation metadata for this field, detected from relevant property elements (constructor
+ * parameter, record component, backing field). {@code null} when the field is not deprecated.
+ */
+ private DeprecationInfoDto deprecationInfo;
+
/**
* Gets the original field name from the DTO. This name is used for generating method names,
* parameter names, and setter method names (e.g., "userName" becomes "setUserName").
@@ -248,22 +255,40 @@ public void setFieldGenerics(List generics) {
}
}
+ /**
+ * Returns the getter info for this field.
+ *
+ * @return the getter info, or {@code null} if no getter was resolved
+ */
+ public GetterInfoDto getGetterInfo() {
+ return getterInfo;
+ }
+
+ /**
+ * Sets the getter info for this field.
+ *
+ * @param getterInfo the getter info to set, or {@code null} to clear
+ */
+ public void setGetterInfo(GetterInfoDto getterInfo) {
+ this.getterInfo = getterInfo;
+ }
+
/**
* Returns the getter method name to use (without parentheses).
*
* @return Optional containing the getter name, or empty if not set
*/
public Optional getGetterName() {
- return Optional.ofNullable(getterName);
+ return Optional.ofNullable(getterInfo).map(GetterInfoDto::getGetterName);
}
/**
- * Sets the getter method name to use (without parentheses).
+ * Checks if the getter method for this field is {@code @Deprecated}.
*
- * @param getterName the getter method name to set
+ * @return {@code true} if the getter is deprecated
*/
- public void setGetterName(String getterName) {
- this.getterName = getterName;
+ public boolean isGetterDeprecated() {
+ return getterInfo != null && getterInfo.deprecated();
}
/**
@@ -353,4 +378,31 @@ public void setDefaultValue(String defaultValue) {
public boolean isRequired() {
return nonNullable && defaultValue == null;
}
+
+ /**
+ * Gets the deprecation metadata for this field.
+ *
+ * @return the deprecation info, or {@code null} if the field is not deprecated
+ */
+ public DeprecationInfoDto getDeprecationInfo() {
+ return deprecationInfo;
+ }
+
+ /**
+ * Sets the deprecation metadata for this field.
+ *
+ * @param deprecationInfo the deprecation info, or {@code null} if not deprecated
+ */
+ public void setDeprecationInfo(DeprecationInfoDto deprecationInfo) {
+ this.deprecationInfo = deprecationInfo;
+ }
+
+ /**
+ * Checks if this field is deprecated (a relevant element is marked {@code @Deprecated}).
+ *
+ * @return {@code true} if the field is deprecated
+ */
+ public boolean isDeprecated() {
+ return deprecationInfo != null && deprecationInfo.isDeprecated();
+ }
}
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/GetterInfoDto.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/GetterInfoDto.java
new file mode 100644
index 00000000..8b17586d
--- /dev/null
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/core/GetterInfoDto.java
@@ -0,0 +1,75 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2026 Andreas Igel
+ *
+ * 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 org.javahelpers.simple.builders.processor.model.core;
+
+import java.util.Objects;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+/**
+ * Encapsulates metadata about the getter method resolved for a field.
+ *
+ * The getter name is needed for the from-instance constructor, which calls the getter to
+ * initialise the builder. The {@code deprecated} flag indicates whether the getter is annotated
+ * {@code @Deprecated}; this is used to decide whether class-level {@code @SuppressWarnings} is
+ * needed, because the from-instance constructor calls the getter internally. Getter deprecation is
+ * intentionally not propagated to generated builder methods.
+ *
+ *
A {@code GetterInfoDto} is only created when a getter was found, so {@link #getterName()} is
+ * always non-null. The absence of a getter is represented by {@code null} on {@link FieldDto}'s
+ * getter info field, not by a {@code GetterInfoDto} with a null name.
+ *
+ * @param getterName the simple name of the getter method; must not be {@code null}
+ * @param deprecated whether the getter method is annotated {@code @Deprecated}
+ */
+public record GetterInfoDto(String getterName, boolean deprecated) {
+
+ /**
+ * Compact constructor enforcing that {@code getterName} is non-null — a {@code GetterInfoDto}
+ * only exists when a getter was found.
+ *
+ * @throws NullPointerException if {@code getterName} is {@code null}
+ */
+ public GetterInfoDto {
+ Objects.requireNonNull(getterName, "getterName must not be null");
+ }
+
+ /**
+ * Returns the getter method name.
+ *
+ * @return the getter name, never {@code null}
+ */
+ public String getGetterName() {
+ return getterName;
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
+ .append("getterName", getterName)
+ .append("deprecated", deprecated)
+ .toString();
+ }
+}
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/javadoc/JavadocDto.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/javadoc/JavadocDto.java
index f3a60eb7..5ff0f3d2 100644
--- a/processor/src/main/java/org/javahelpers/simple/builders/processor/model/javadoc/JavadocDto.java
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/model/javadoc/JavadocDto.java
@@ -228,6 +228,28 @@ public JavadocDto addThrows(String exceptionName, String descriptionFormat, Obje
return this;
}
+ /**
+ * Adds a @deprecated tag with optional formatted description.
+ *
+ *
Supports String.format-style formatting when args are provided.
+ *
+ *
Examples:
+ *
+ *
{@code
+ * addDeprecated("use {@link #setLabel(String)} instead")
+ * addDeprecated("use {%s} instead", replacement)
+ * }
+ *
+ * @param descriptionFormat the format string for the deprecation description
+ * @param args optional arguments referenced by format specifiers in the description
+ * @return this JavadocDto for fluent chaining
+ */
+ public JavadocDto addDeprecated(String descriptionFormat, Object... args) {
+ String deprecatedDescription = String.format(descriptionFormat, args);
+ addTag("deprecated", deprecatedDescription);
+ return this;
+ }
+
/**
* Appends additional text to the existing description.
*
diff --git a/processor/src/main/java/org/javahelpers/simple/builders/processor/processing/BuilderDefinitionCreator.java b/processor/src/main/java/org/javahelpers/simple/builders/processor/processing/BuilderDefinitionCreator.java
index a722e76c..fc1b80cc 100644
--- a/processor/src/main/java/org/javahelpers/simple/builders/processor/processing/BuilderDefinitionCreator.java
+++ b/processor/src/main/java/org/javahelpers/simple/builders/processor/processing/BuilderDefinitionCreator.java
@@ -27,6 +27,7 @@
import static java.util.stream.Collectors.toSet;
import static org.javahelpers.simple.builders.processor.analysis.JavaLangAnalyser.*;
import static org.javahelpers.simple.builders.processor.analysis.JavaLangMapper.map2MethodParameter;
+import static org.javahelpers.simple.builders.processor.analysis.JavaLangMapper.map2TypeName;
import static org.javahelpers.simple.builders.processor.processing.AnnotationValidator.validateAnnotatedElement;
import java.util.ArrayList;
@@ -38,7 +39,9 @@
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import javax.lang.model.element.*;
+import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Strings;
@@ -53,9 +56,12 @@
import org.javahelpers.simple.builders.processor.model.annotation.AnnotationDto;
import org.javahelpers.simple.builders.processor.model.core.BuilderDefinitionDto;
import org.javahelpers.simple.builders.processor.model.core.ClassFieldDto;
+import org.javahelpers.simple.builders.processor.model.core.DeprecationInfoDto;
import org.javahelpers.simple.builders.processor.model.core.FieldDto;
+import org.javahelpers.simple.builders.processor.model.core.GetterInfoDto;
import org.javahelpers.simple.builders.processor.model.javadoc.JavadocDto;
import org.javahelpers.simple.builders.processor.model.method.BuilderMethodDto;
+import org.javahelpers.simple.builders.processor.model.method.ConstructorDto;
import org.javahelpers.simple.builders.processor.model.method.MethodParameterDto;
import org.javahelpers.simple.builders.processor.model.type.TypeName;
import org.javahelpers.simple.builders.processor.model.type.TypeNameGeneric;
@@ -66,6 +72,9 @@ public class BuilderDefinitionCreator {
/** Annotation simple names recognized as default-value annotations, regardless of package. */
private static final Set DEFAULT_ANNOTATION_NAMES = Set.of("Default", "DefaultValue");
+ /** TypeName for {@link Deprecated}. */
+ private static final TypeName DEPRECATED_TYPE = map2TypeName(Deprecated.class);
+
private BuilderDefinitionCreator() {
// Private constructor to prevent instantiation
}
@@ -102,6 +111,16 @@ public static BuilderDefinitionDto extractFromElement(
// Apply builder enhancers (including With interface generation)
context.getGeneratorRegistry().enhanceBuilder(result, result.getBuildingTargetTypeName());
+ // Propagate @Deprecated from the DTO type to the generated builder class and its factory
+ // methods. Done after enhancement so the class javadoc, create() method and constructors
+ // already exist and can be annotated.
+ applyDtoDeprecation(result, annotatedType, context);
+
+ // Suppress deprecation warnings inside generated code that legitimately calls deprecated
+ // DTO members (e.g. build() calls deprecated setters/constructor, from-instance constructor
+ // calls deprecated getters).
+ applyDeprecationSuppressions(result, annotatedType, context);
+
// Finalize the definition - convert to generic class representation
finalizeDefinition(result, context);
@@ -791,9 +810,14 @@ private static Optional createFieldDto(
// Find matching getter on the DTO type using the builder field name
TypeElement dtoTypeElement = context.getTypeElement(dtoType);
- JavaLangAnalyser.findGetterForField(
- dtoTypeElement, fieldNameInBuilder, fieldTypeMirror, context)
- .ifPresent(getter -> field.setGetterName(getter.getSimpleName().toString()));
+ Optional getter =
+ JavaLangAnalyser.findGetterForField(
+ dtoTypeElement, fieldNameInBuilder, fieldTypeMirror, context);
+ getter.ifPresent(
+ g ->
+ field.setGetterInfo(
+ new GetterInfoDto(
+ g.getSimpleName().toString(), g.getAnnotation(Deprecated.class) != null)));
// Extract annotations from the field parameter
List annotations = FieldAnnotationExtractor.extractAnnotations(param, context);
@@ -818,6 +842,11 @@ private static Optional createFieldDto(
field.setDefaultValue(
FieldAnnotationExtractor.formatDefaultExpression(rawDefault, fieldType)));
+ // Detect @Deprecated on relevant property elements (constructor parameter, record
+ // component, setter method, or the field type itself) and propagate it to all generated
+ // builder methods for this field.
+ detectAndApplyFieldDeprecation(field, param, fieldTypeMirror, dtoTypeElement, context);
+
// Builder and constructor information is now set when TypeName is created in JavaLangMapper
// Use GeneratorRegistry to generate all methods for this field
@@ -825,9 +854,330 @@ private static Optional createFieldDto(
context.getGeneratorRegistry().generateAllMethods(field, dtoType, builderType);
generatedMethods.forEach(field::addMethod);
+ // Apply @Deprecated annotation and @deprecated javadoc to every generated builder method for
+ // this field (basic setter, supplier, consumer, collection helpers, varargs, with…).
+ if (field.isDeprecated()) {
+ generatedMethods.forEach(
+ method -> applyDeprecationToMethod(method, field.getDeprecationInfo()));
+ }
+
return Optional.of(field);
}
+ /**
+ * Detects {@code @Deprecated} on relevant property elements (constructor parameter, record
+ * component, setter method, field type) and records the result as a {@link DeprecationInfoDto} on
+ * the {@link FieldDto}.
+ *
+ * The first element that carries {@code @Deprecated} (in the order: parameter, record
+ * component, setter, field type) provides the annotation DTO (preserving {@code since} and {@code
+ * forRemoval} attributes). The {@code @deprecated} javadoc text is extracted from the enclosing
+ * executable (setter method or constructor) of the parameter, falling back to the record
+ * component or the field type's Javadoc.
+ *
+ *
Note: getter methods and backing fields are intentionally not propagation sources.
+ * A deprecated getter means "don't call this accessor directly anymore", but the builder does not
+ * expose a get-API — only the from-instance constructor calls the getter internally, which is
+ * covered by the class-level {@code @SuppressWarnings}. The backing field is not checked because
+ * it is uncommon to deprecate a field without also deprecating the setter or constructor
+ * parameter. The setter, however, IS a propagation source because the builder's fluent methods
+ * replace the setter as the write API for the property. The field type is also a propagation
+ * source because the builder method exposes the deprecated type in its parameter — consumers
+ * calling the method would get deprecation warnings.
+ *
+ * @param field the field DTO to update
+ * @param param the constructor or setter parameter element
+ * @param fieldTypeMirror the type mirror of the field/parameter type
+ * @param dtoTypeElement the DTO type element
+ * @param context processing context
+ */
+ private static void detectAndApplyFieldDeprecation(
+ FieldDto field,
+ VariableElement param,
+ TypeMirror fieldTypeMirror,
+ TypeElement dtoTypeElement,
+ ProcessingContext context) {
+ if (dtoTypeElement == null) {
+ return;
+ }
+ String fieldName = field.getOriginalFieldName();
+
+ Optional extends Element> recordComponent =
+ JavaLangAnalyser.findRecordComponent(dtoTypeElement, fieldName);
+ Optional setter =
+ JavaLangAnalyser.findSetterForField(dtoTypeElement, fieldName, context);
+
+ // Search the relevant elements in priority order for the @Deprecated annotation to propagate.
+ // Note: the backing field is intentionally NOT checked — it is uncommon to deprecate a
+ // field without also deprecating the setter or constructor parameter, and checking the
+ // field adds complexity without practical value.
+ record DeprecationMatch(Element source, AnnotationDto annotation) {}
+
+ Optional match =
+ presentElements(
+ Optional.of(param),
+ recordComponent,
+ setter,
+ resolveFieldTypeElement(fieldTypeMirror))
+ .map(
+ element ->
+ FieldAnnotationExtractor.extractDeprecatedAnnotation(element, context)
+ .map(annot -> new DeprecationMatch(element, annot)))
+ .flatMap(Optional::stream)
+ .findFirst();
+
+ if (match.isEmpty()) {
+ return;
+ }
+ DeprecationMatch deprecation = match.get();
+
+ // Extract the @deprecated javadoc text. Prefer the enclosing executable (setter/constructor)
+ // of the parameter, then the setter method, then the record component, then the field type.
+ Element enclosing = param.getEnclosingElement();
+ String deprecatedJavaDoc =
+ presentElements(
+ Optional.ofNullable(enclosing),
+ setter,
+ recordComponent,
+ asTypeElement(deprecation.source()))
+ .map(
+ element ->
+ JavaLangAnalyser.extractDeprecatedJavaDoc(context.getDocComment(element)))
+ .filter(java.util.Objects::nonNull)
+ .findFirst()
+ .orElse(null);
+
+ field.setDeprecationInfo(new DeprecationInfoDto(deprecation.annotation(), deprecatedJavaDoc));
+
+ context.debug(
+ "Field '%s' is deprecated (source: %s)", fieldName, deprecation.source().getSimpleName());
+ }
+
+ /**
+ * Filters out empty optionals and returns a stream of present elements.
+ *
+ * @param optionals variable number of optional elements
+ * @return stream of elements that are present in their optionals
+ */
+ @SafeVarargs
+ private static Stream presentElements(Optional extends Element>... optionals) {
+ return Stream.of(optionals).flatMap(Optional::stream).map(e -> (Element) e);
+ }
+
+ /**
+ * Returns the element as an {@link Optional} if it is a {@link TypeElement}, otherwise empty.
+ *
+ * @param element the element to check
+ * @return optional containing the element if it is a TypeElement, otherwise empty
+ */
+ private static Optional asTypeElement(Element element) {
+ return element instanceof TypeElement typeElement ? Optional.of(typeElement) : Optional.empty();
+ }
+
+ /**
+ * Resolves the {@link TypeElement} of a field type if it is a declared type.
+ *
+ * @param fieldTypeMirror the field type mirror
+ * @return optional containing the type element, or empty if not a declared type
+ */
+ private static Optional resolveFieldTypeElement(TypeMirror fieldTypeMirror) {
+ if (fieldTypeMirror instanceof DeclaredType declaredType
+ && declaredType.asElement() instanceof TypeElement typeElement) {
+ return Optional.of(typeElement);
+ }
+ return Optional.empty();
+ }
+
+ /**
+ * Applies the {@code @Deprecated} annotation and {@code @deprecated} javadoc tag to a generated
+ * builder method for a deprecated field.
+ *
+ * @param method the generated builder method to annotate
+ * @param deprecationInfo the deprecation info providing the annotation and javadoc text
+ */
+ private static void applyDeprecationToMethod(
+ BuilderMethodDto method, DeprecationInfoDto deprecationInfo) {
+ deprecationInfo.getDeprecatedAnnotation().ifPresent(method::addAnnotation);
+ deprecationInfo
+ .getDeprecatedJavaDoc()
+ .ifPresent(text -> method.getJavadoc().addDeprecated(text));
+ }
+
+ /**
+ * Propagates {@code @Deprecated} from the DTO type to the generated builder class and its factory
+ * methods (static {@code create()} and constructors).
+ *
+ * When the DTO type is deprecated, the builder class itself, the {@code create()} factory
+ * method and all constructors are annotated with {@code @Deprecated} and the {@code @deprecated}
+ * javadoc text from the DTO is copied into their javadoc.
+ *
+ * @param builderDto the builder definition to update
+ * @param annotatedType the annotated DTO type element
+ * @param context processing context
+ */
+ private static void applyDtoDeprecation(
+ BuilderDefinitionDto builderDto, TypeElement annotatedType, ProcessingContext context) {
+ Optional deprecatedAnnot =
+ FieldAnnotationExtractor.extractDeprecatedAnnotation(annotatedType, context);
+ if (deprecatedAnnot.isEmpty()) {
+ return;
+ }
+ AnnotationDto deprecated = deprecatedAnnot.get();
+ String deprecatedJavaDoc =
+ JavaLangAnalyser.extractDeprecatedJavaDoc(context.getDocComment(annotatedType));
+
+ // Annotate the builder class
+ builderDto.addClassAnnotation(deprecated);
+ addDeprecatedJavadoc(builderDto.getClassJavadoc(), deprecatedJavaDoc);
+
+ // Annotate the static create() factory method
+ for (BuilderMethodDto method : builderDto.getMethods()) {
+ if (method.isStatic() && "create".equals(method.getMethodName())) {
+ method.addAnnotation(deprecated);
+ addDeprecatedJavadoc(method.getJavadoc(), deprecatedJavaDoc);
+ }
+ }
+
+ // Annotate constructors (factory entry points)
+ for (ConstructorDto constructor : builderDto.getConstructors()) {
+ constructor.addAnnotation(deprecated);
+ addDeprecatedJavadoc(constructor.getJavadoc(), deprecatedJavaDoc);
+ }
+
+ context.debug("DTO type is deprecated; builder class and factory methods annotated");
+ }
+
+ /**
+ * Adds the {@code @deprecated} javadoc tag to the given javadoc.
+ *
+ * The javadoc is guaranteed to be non-null because this method is called after the generation
+ * pipeline has run, which always creates javadoc for the builder class, factory methods, and
+ * constructors.
+ *
+ * @param javadoc the existing javadoc (non-null)
+ * @param deprecatedJavaDoc the deprecated text, or {@code null} to do nothing
+ */
+ private static void addDeprecatedJavadoc(JavadocDto javadoc, String deprecatedJavaDoc) {
+ if (deprecatedJavaDoc == null) {
+ return;
+ }
+ javadoc.addDeprecated(deprecatedJavaDoc);
+ }
+
+ /**
+ * Adds a class-level {@code @SuppressWarnings({"deprecation", "removal"})} to the generated
+ * builder when any internal code legitimately calls deprecated DTO members or uses deprecated
+ * types, so the generated builder compiles without deprecation warnings.
+ *
+ *
A single class-level annotation covers all internal call sites at once:
+ *
+ *
+ * - {@code build()} calls the DTO constructor and setter methods.
+ *
- {@code create()} instantiates the builder class (deprecated when the DTO is).
+ *
- The from-instance constructor calls the DTO getter methods.
+ *
- Nested types (e.g. the {@code With} interface) reference the deprecated DTO/builder types
+ * in their default method implementations.
+ *
- Collection helper methods call {@code create()} on deprecated element builders.
+ *
- Builder methods for fields with deprecated types internally use the deprecated type.
+ *
+ *
+ * The class-level suppression only silences warnings about deprecated members used
+ * inside the builder; it does not affect warnings shown to callers of the
+ * builder's own {@code @Deprecated} methods.
+ *
+ * @param builderDto the builder definition to update
+ * @param annotatedType the annotated DTO type element
+ * @param context processing context
+ */
+ private static void applyDeprecationSuppressions(
+ BuilderDefinitionDto builderDto, TypeElement annotatedType, ProcessingContext context) {
+ boolean dtoDeprecated = annotatedType.getAnnotation(Deprecated.class) != null;
+ boolean constructorDeprecated = isConstructorDeprecated(annotatedType, context);
+ // Check getter method deprecation from the flag stored during field creation — a deprecated
+ // getter triggers compiler warnings in generated code (the from-instance constructor calls
+ // getters). Getter deprecation is intentionally NOT propagated to builder methods, so it
+ // must be checked separately from anyMethodDeprecated.
+ boolean anyGetterMethodDeprecated =
+ builderDto.getAllFieldsForBuilder().stream().anyMatch(FieldDto::isGetterDeprecated);
+ // If any generated builder method is @Deprecated (e.g. because the field type, parameter,
+ // setter, or record component is deprecated), other generated methods within the builder
+ // (varargs helpers, add-to-collection helpers, etc.) may call that deprecated method
+ // internally, so class-level suppression is needed. Deprecated setters are covered here
+ // because setter deprecation propagates to the generated builder methods.
+ boolean anyMethodDeprecated =
+ builderDto.getAllFieldsForBuilder().stream()
+ .flatMap(f -> f.getMethods().stream())
+ .flatMap(m -> m.getAnnotations().stream())
+ .map(AnnotationDto::getAnnotationType)
+ .anyMatch(DEPRECATED_TYPE::equals);
+ // Check if any collection field's element type is @Deprecated. The generated collection
+ // helper calls the element builder's create() method internally, which would produce
+ // deprecation warnings if the element builder is @Deprecated (which it will be if the
+ // element DTO is @Deprecated). May over-match slightly (element type deprecated but no
+ // element builder generated), but the extra @SuppressWarnings is harmless.
+ boolean anyElementTypeDeprecated =
+ builderDto.getAllFieldsForBuilder().stream()
+ .filter(f -> f.getFieldType() instanceof TypeNameGeneric)
+ .map(f -> ((TypeNameGeneric) f.getFieldType()).getInnerType())
+ .filter(Optional::isPresent)
+ .map(Optional::get)
+ .map(context::getTypeElement)
+ .filter(java.util.Objects::nonNull)
+ .anyMatch(elementType -> elementType.getAnnotation(Deprecated.class) != null);
+
+ boolean needsSuppress =
+ dtoDeprecated
+ || constructorDeprecated
+ || anyMethodDeprecated
+ || anyGetterMethodDeprecated
+ || anyElementTypeDeprecated;
+
+ if (!needsSuppress) {
+ return;
+ }
+
+ AnnotationDto suppressWarnings = createSuppressWarningsAnnotation();
+ builderDto.addClassAnnotation(suppressWarnings);
+
+ context.debug(
+ "Added class-level @SuppressWarnings for deprecation (dtoDeprecated=%s, ctorDeprecated=%s, methodDeprecated=%s, getterDeprecated=%s, elementTypeDeprecated=%s)",
+ dtoDeprecated,
+ constructorDeprecated,
+ anyMethodDeprecated,
+ anyGetterMethodDeprecated,
+ anyElementTypeDeprecated);
+ }
+
+ /**
+ * Checks whether the constructor selected for builder generation is {@code @Deprecated}.
+ *
+ * @param annotatedType the annotated DTO type element
+ * @param context processing context
+ * @return {@code true} if the selected constructor is deprecated
+ */
+ private static boolean isConstructorDeprecated(
+ TypeElement annotatedType, ProcessingContext context) {
+ return JavaLangAnalyser.findConstructorForBuilder(annotatedType, context)
+ .filter(ctor -> ctor.getAnnotation(Deprecated.class) != null)
+ .isPresent();
+ }
+
+ /**
+ * Creates a {@code @SuppressWarnings("deprecation")} annotation DTO.
+ *
+ *
Note: when {@code @Deprecated(forRemoval = true)} is used, the compiler emits "removal"
+ * warnings instead of (or in addition to) "deprecation" warnings. To cover both cases, the
+ * suppression includes both {@code "deprecation"} and {@code "removal"}.
+ *
+ * @return the annotation DTO
+ */
+ private static AnnotationDto createSuppressWarningsAnnotation() {
+ AnnotationDto annotation = new AnnotationDto();
+ annotation.setAnnotationType(new TypeName("java.lang", "SuppressWarnings"));
+ annotation.addMember("value", "{\"deprecation\", \"removal\"}");
+ return annotation;
+ }
+
/**
* Attempts to extract and apply a default value from the field declaration itself, if the field
* carries a recognized default annotation (e.g. {@code @Default}). Used as a fallback when no
@@ -852,20 +1202,4 @@ private static void tryApplyDefaultFromField(
field.setDefaultValue(
FieldAnnotationExtractor.formatDefaultExpression(rawDefault.get(), field.getFieldType()));
}
-
- /**
- * Finds a field element by name in the given class element.
- *
- * @param classElement the class to search in
- * @param fieldName the simple field name to look for
- * @return an {@link Optional} containing the field element, or empty if not found
- */
- private static Optional findFieldElement(
- TypeElement classElement, String fieldName) {
- return classElement.getEnclosedElements().stream()
- .filter(e -> e.getKind() == ElementKind.FIELD)
- .filter(e -> e.getSimpleName().contentEquals(fieldName))
- .map(VariableElement.class::cast)
- .findFirst();
- }
}
diff --git a/processor/src/test/java/org/javahelpers/simple/builders/processor/AnnotationCopyTest.java b/processor/src/test/java/org/javahelpers/simple/builders/processor/AnnotationCopyTest.java
index c78b800a..acdab6c8 100644
--- a/processor/src/test/java/org/javahelpers/simple/builders/processor/AnnotationCopyTest.java
+++ b/processor/src/test/java/org/javahelpers/simple/builders/processor/AnnotationCopyTest.java
@@ -304,13 +304,13 @@ void annotations_deprecatedCopied_suppressWarningsFiltered() {
JavaFileObject service =
JavaFileObjects.forSourceString(
- packageName + ".Service",
+ packageName + ".MyDto",
"""
package test.javafilter;
import org.javahelpers.simple.builders.core.annotations.SimpleBuilder;
@SimpleBuilder
- public class Service {
+ public class MyDto {
private String name;
public String getName() { return name; }
@@ -324,15 +324,591 @@ public void setName(
""");
Compilation compilation = compileSources(service);
- String generatedCode = loadGeneratedSource(compilation, "ServiceBuilder");
- ProcessorAsserts.assertGenerationSucceeded(compilation, "ServiceBuilder", generatedCode);
+ String generatedCode = loadGeneratedSource(compilation, "MyDtoBuilder");
+ ProcessorAsserts.assertGenerationSucceeded(compilation, "MyDtoBuilder", generatedCode);
- // Verify that @Deprecated IS copied (developers need to know field is deprecated!)
+ // @Deprecated is applied to the generated builder method itself (not the parameter —
+ // the parameter is a new declaration, the deprecation is about the property).
ProcessorAsserts.assertingResult(
- generatedCode, contains("public ServiceBuilder name(@Deprecated String name)"));
+ generatedCode,
+ contains(
+ """
+ @Deprecated
+ public MyDtoBuilder name(String name) {"""));
+
+ // Class-level @SuppressWarnings is present because the deprecated builder method may be
+ // called internally by other generated methods (varargs helpers, etc.).
+ ProcessorAsserts.assertingResult(
+ generatedCode,
+ contains(
+ """
+ @SuppressWarnings({"deprecation", "removal"})
+ public class MyDtoBuilder implements IBuilderBase"""));
+ }
+
+ @Test
+ void annotations_deprecatedSetterMethod_methodLevelDeprecatedAndBuildSuppressed() {
+ String packageName = "test.deprecated.setter";
+
+ JavaFileObject service =
+ JavaFileObjects.forSourceString(
+ packageName + ".MyDto",
+ """
+ package test.deprecated.setter;
+ import org.javahelpers.simple.builders.core.annotations.SimpleBuilder;
+
+ @SimpleBuilder
+ public class MyDto {
+ private String name;
+
+ public String getName() { return name; }
+
+ @Deprecated
+ public void setName(String name) {
+ this.name = name;
+ }
+ }
+ """);
+
+ Compilation compilation = compileSources(service);
+ String generatedCode = loadGeneratedSource(compilation, "MyDtoBuilder");
+ ProcessorAsserts.assertGenerationSucceeded(compilation, "MyDtoBuilder", generatedCode);
+
+ // The generated builder method IS @Deprecated because the setter is the write API for the
+ // property and the builder method replaces it — deprecation propagates.
+ ProcessorAsserts.assertingResult(
+ generatedCode,
+ contains(
+ """
+ @Deprecated
+ public MyDtoBuilder name(String name) {"""));
+
+ // build() calls the deprecated setter via result::setName; the whole builder class carries a
+ // class-level @SuppressWarnings so all internal calls to deprecated members are silenced.
+ ProcessorAsserts.assertingResult(
+ generatedCode,
+ contains(
+ """
+ @SuppressWarnings({"deprecation", "removal"})
+ public class MyDtoBuilder implements IBuilderBase"""));
+ }
+
+ @Test
+ void annotations_deprecatedRecordComponent_methodLevelDeprecated() {
+ String packageName = "test.deprecated.record";
+
+ JavaFileObject book =
+ JavaFileObjects.forSourceString(
+ packageName + ".Book",
+ """
+ package test.deprecated.record;
+ import org.javahelpers.simple.builders.core.annotations.SimpleBuilder;
+
+ @SimpleBuilder
+ public record Book(@Deprecated String title, int pages) {}
+ """);
+
+ Compilation compilation = compileSources(book);
+ String generatedCode = loadGeneratedSource(compilation, "BookBuilder");
+ ProcessorAsserts.assertGenerationSucceeded(compilation, "BookBuilder", generatedCode);
+
+ // The generated builder method for the deprecated record component is @Deprecated (on the
+ // method, not the parameter — the parameter is a new declaration).
+ ProcessorAsserts.assertingResult(
+ generatedCode,
+ contains(
+ """
+ @Deprecated
+ public BookBuilder title(String title) {"""));
+
+ // The non-deprecated field is not annotated — include the preceding Javadoc closing so
+ // that an @Deprecated annotation between them would break the match.
+ ProcessorAsserts.assertingResult(
+ generatedCode,
+ contains(
+ """
+ */
+ public BookBuilder pages(int pages) {"""));
+ }
+
+ @Test
+ void annotations_deprecatedDtoClass_builderAndFactoryMethodsDeprecated() {
+ String packageName = "test.deprecated.dto";
+
+ // The DTO class itself is @Deprecated. This must propagate @Deprecated to the generated
+ // builder class, both constructors, and the create() factory method. The builder class
+ // also needs class-level @SuppressWarnings because it internally instantiates the
+ // deprecated DTO and calls its constructor. The field-level builder method (name()) and
+ // build() are NOT @Deprecated because only the class is deprecated, not the field/param/
+ // setter. Most builder options are DISABLED to keep the generated code minimal enough for
+ // a single full text-block comparison.
+ JavaFileObject service =
+ JavaFileObjects.forSourceString(
+ packageName + ".MyDto",
+ """
+ package test.deprecated.dto;
+ import org.javahelpers.simple.builders.core.annotations.SimpleBuilder;
+ import org.javahelpers.simple.builders.core.enums.OptionState;
+
+ @Deprecated
+ @SimpleBuilder(options = @SimpleBuilder.Options(
+ generateFieldSupplier = OptionState.DISABLED,
+ generateFieldConsumer = OptionState.DISABLED,
+ generateBuilderConsumer = OptionState.DISABLED,
+ generateConditionalHelper = OptionState.DISABLED,
+ generateVarArgsHelpers = OptionState.DISABLED,
+ generateStringFormatHelpers = OptionState.DISABLED,
+ generateAddToCollectionHelpers = OptionState.DISABLED,
+ generateWithInterface = OptionState.DISABLED
+ ))
+ public class MyDto {
+ private String name;
+
+ public String getName() { return name; }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+ }
+ """);
+
+ Compilation compilation = compileSources(service);
+ String generatedCode = loadGeneratedSource(compilation, "MyDtoBuilder");
+ ProcessorAsserts.assertGenerationSucceeded(compilation, "MyDtoBuilder", generatedCode);
+
+ // Full text-block comparison of the generated builder class. With most options disabled,
+ // the output is minimal enough to compare comprehensively. The text block includes the
+ // complete class from annotations to closing brace — imports are omitted as they are not
+ // relevant to the deprecation feature.
+ ProcessorAsserts.assertContaining(
+ generatedCode,
+ """
+ @Generated("Generated by org.javahelpers.simple.builders.processor.BuilderProcessor")
+ @BuilderImplementation(forClass = MyDto.class)
+ @Deprecated
+ @SuppressWarnings({"deprecation", "removal"})
+ public class MyDtoBuilder implements IBuilderBase {
+
+ /**
+ * Tracked value for name: name.
+ */
+ private TrackedValue name = unsetValue();
+
+ /**
+ * Empty constructor of builder for {@code test.deprecated.dto.MyDto}.
+ */
+ @Deprecated
+ public MyDtoBuilder() {
+ }
+
+ /**
+ * Initialisation of builder for {@code test.deprecated.dto.MyDto} by a instance.
+ *
+ * @param instance object instance for initialisiation
+ */
+ @Deprecated
+ public MyDtoBuilder(MyDto instance) {
+ this.name = initialValue(instance.getName());
+ }
+
+ /**
+ * Creating a new builder for {@code test.deprecated.dto.MyDto}.
+ *
+ * Example:
+ *
+ * {@code
+ * MyDtoBuilder builder = MyDtoBuilder.create();
+ * }
+ *
+ * @return builder for {@code test.deprecated.dto.MyDto}
+ */
+ @Deprecated
+ public static MyDtoBuilder create() {
+ return new MyDtoBuilder();
+ }
+
+ /**
+ * Sets the value for name.
+ *
+ * Generated from setter {@link MyDto#setName(String) setName(String name)}
+ *
+ *
Example:
+ *
+ * {@code
+ * builder.name("example value");
+ * }
+ *
+ * @param name name
+ * @return current instance of builder
+ */
+ public MyDtoBuilder name(String name) {
+ this.name = changedValue(name);
+ return this;
+ }
+
+ /**
+ * Builds the configured DTO instance.
+ *
+ * Example:
+ *
+ * {@code
+ * MyDto result = builder.build();
+ * }
+ */
+ @Override
+ public MyDto build() {
+ MyDto result = new MyDto();
+ this.name.ifSet(result::setName);
+ return result;
+ }
+
+ /**
+ * Returns a string representation of this builder, including only fields that have been set.
+ *
+ * @return string representation of the builder
+ */
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this, BuilderToStringStyle.INSTANCE).append("name", this.name).toString();
+ }
+ }
+ """);
+ }
+
+ @Test
+ void annotations_deprecatedDtoClassWithJavadoc_deprecatedJavadocPropagatedToBuilderClass() {
+ String packageName = "test.deprecated.dto.javadoc";
+
+ // The DTO class is @Deprecated AND has an @deprecated javadoc tag. The @deprecated javadoc
+ // text must be propagated to the generated builder class javadoc, the create() factory method
+ // javadoc, and the constructor javadoc. This covers the case where addDeprecatedJavadoc is
+ // called with a non-null deprecatedJavaDoc and a potentially null pre-existing javadoc.
+ JavaFileObject myDto =
+ JavaFileObjects.forSourceString(
+ packageName + ".MyDto",
+ """
+ package test.deprecated.dto.javadoc;
+ import org.javahelpers.simple.builders.core.annotations.SimpleBuilder;
+ import org.javahelpers.simple.builders.core.enums.OptionState;
+
+ /**
+ * A DTO that is now obsolete.
+ *
+ * @deprecated use {@link NewDto} instead
+ */
+ @Deprecated
+ @SimpleBuilder(options = @SimpleBuilder.Options(
+ generateFieldSupplier = OptionState.DISABLED,
+ generateFieldConsumer = OptionState.DISABLED,
+ generateBuilderConsumer = OptionState.DISABLED,
+ generateConditionalHelper = OptionState.DISABLED,
+ generateVarArgsHelpers = OptionState.DISABLED,
+ generateStringFormatHelpers = OptionState.DISABLED,
+ generateAddToCollectionHelpers = OptionState.DISABLED,
+ generateWithInterface = OptionState.DISABLED
+ ))
+ public class MyDto {
+ private String name;
+
+ public String getName() { return name; }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+ }
+ """);
+
+ Compilation compilation = compileSources(myDto);
+ String generatedCode = loadGeneratedSource(compilation, "MyDtoBuilder");
+ ProcessorAsserts.assertGenerationSucceeded(compilation, "MyDtoBuilder", generatedCode);
+
+ // The @deprecated javadoc from the DTO class is propagated to the builder class
+ ProcessorAsserts.assertingResult(
+ generatedCode,
+ contains(
+ """
+ * @deprecated use {@link NewDto} instead"""));
+
+ // The builder class itself is @Deprecated (with @SuppressWarnings in between)
+ ProcessorAsserts.assertingResult(
+ generatedCode,
+ contains(
+ """
+ @Deprecated
+ @SuppressWarnings({"deprecation", "removal"})
+ public class MyDtoBuilder implements IBuilderBase"""));
+ }
+
+ @Test
+ void annotations_deprecatedJavadocText_propagatedToBuilderMethods() {
+ String packageName = "test.deprecated.javadoc";
+
+ JavaFileObject service =
+ JavaFileObjects.forSourceString(
+ packageName + ".MyDto",
+ """
+ package test.deprecated.javadoc;
+ import org.javahelpers.simple.builders.core.annotations.SimpleBuilder;
+
+ @SimpleBuilder
+ public class MyDto {
+ private String name;
+
+ public String getName() { return name; }
+
+ /**
+ * Sets the name.
+ *
+ * @deprecated use {@link #label} instead
+ */
+ @Deprecated
+ public void setName(String name) {
+ this.name = name;
+ }
+ }
+ """);
- // But @SuppressWarnings is NOT copied (compiler-only, not relevant for users)
- ProcessorAsserts.assertNotContaining(generatedCode, "@SuppressWarnings");
+ Compilation compilation = compileSources(service);
+ String generatedCode = loadGeneratedSource(compilation, "MyDtoBuilder");
+ ProcessorAsserts.assertGenerationSucceeded(compilation, "MyDtoBuilder", generatedCode);
+
+ // The @deprecated javadoc text is propagated to the generated builder method
+ ProcessorAsserts.assertingResult(
+ generatedCode,
+ contains(
+ """
+ * @deprecated use {@link #label} instead"""));
+
+ // The method itself is @Deprecated (detected from the deprecated setter)
+ ProcessorAsserts.assertingResult(
+ generatedCode,
+ contains(
+ """
+ @Deprecated
+ public MyDtoBuilder name(String name) {"""));
+ }
+
+ @Test
+ void annotations_deprecatedWithSinceAndForRemoval_attributesPreserved() {
+ String packageName = "test.deprecated.attrs";
+
+ JavaFileObject service =
+ JavaFileObjects.forSourceString(
+ packageName + ".MyDto",
+ """
+ package test.deprecated.attrs;
+ import org.javahelpers.simple.builders.core.annotations.SimpleBuilder;
+
+ @SimpleBuilder
+ public class MyDto {
+ private String name;
+
+ public String getName() { return name; }
+
+ @Deprecated(since = "1.2", forRemoval = true)
+ public void setName(String name) {
+ this.name = name;
+ }
+ }
+ """);
+
+ Compilation compilation = compileSources(service);
+ String generatedCode = loadGeneratedSource(compilation, "MyDtoBuilder");
+ ProcessorAsserts.assertGenerationSucceeded(compilation, "MyDtoBuilder", generatedCode);
+
+ // The since and forRemoval attributes are preserved on the generated builder method
+ ProcessorAsserts.assertingResult(
+ generatedCode,
+ contains(
+ """
+ @Deprecated(since = "1.2", forRemoval = true)
+ public MyDtoBuilder name(String name) {"""));
+ }
+
+ @Test
+ void annotations_deprecatedGetter_fromInstanceConstructorSuppressed() {
+ String packageName = "test.deprecated.getter";
+
+ JavaFileObject service =
+ JavaFileObjects.forSourceString(
+ packageName + ".MyDto",
+ """
+ package test.deprecated.getter;
+ import org.javahelpers.simple.builders.core.annotations.SimpleBuilder;
+
+ @SimpleBuilder
+ public class MyDto {
+ private String name;
+
+ @Deprecated
+ public String getName() { return name; }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+ }
+ """);
+
+ Compilation compilation = compileSources(service);
+ String generatedCode = loadGeneratedSource(compilation, "MyDtoBuilder");
+ ProcessorAsserts.assertGenerationSucceeded(compilation, "MyDtoBuilder", generatedCode);
+
+ // A deprecated getter does NOT propagate @Deprecated to the generated builder method
+ // (deprecated getter != deprecated property). The class carries a class-level
+ // @SuppressWarnings because the from-instance constructor calls the deprecated getter.
+ // The constructor itself does NOT carry its own @SuppressWarnings — suppression is only
+ // at class level.
+ ProcessorAsserts.assertingResult(
+ generatedCode,
+ // name() is not @Deprecated — include preceding Javadoc closing to prove it
+ contains(
+ """
+ */
+ public MyDtoBuilder name(String name) {"""),
+ // Class-level @SuppressWarnings is present
+ contains(
+ """
+ @SuppressWarnings({"deprecation", "removal"})
+ public class MyDtoBuilder implements IBuilderBase"""),
+ // From-instance constructor: no own @SuppressWarnings (preceding Javadoc closing),
+ // and the body calls the deprecated getter (instance.getName())
+ contains(
+ """
+ */
+ public MyDtoBuilder(MyDto instance) {
+ this.name = initialValue(instance.getName());
+ }"""));
+ }
+
+ @Test
+ void annotations_deprecatedFieldType_methodLevelDeprecatedAndClassSuppressed() {
+ String packageName = "test.deprecated.fieldtype";
+
+ // The field type itself is @Deprecated. The builder method for this field should be
+ // @Deprecated (consumers calling it would get deprecation warnings from the deprecated
+ // parameter type), and the class needs @SuppressWarnings because the builder internally
+ // uses the deprecated type.
+ JavaFileObject dto =
+ JavaFileObjects.forSourceString(
+ packageName + ".MyDto",
+ """
+ package test.deprecated.fieldtype;
+ import org.javahelpers.simple.builders.core.annotations.SimpleBuilder;
+
+ @SimpleBuilder
+ public class MyDto {
+ private OldType value;
+
+ public OldType getValue() { return value; }
+
+ public void setValue(OldType value) {
+ this.value = value;
+ }
+ }
+ """);
+
+ JavaFileObject oldType =
+ JavaFileObjects.forSourceString(
+ packageName + ".OldType",
+ """
+ package test.deprecated.fieldtype;
+
+ @Deprecated
+ public class OldType {
+ private String data;
+
+ public OldType() {}
+
+ public String getData() { return data; }
+
+ public void setData(String data) {
+ this.data = data;
+ }
+ }
+ """);
+
+ Compilation compilation = compileSources(dto, oldType);
+ String generatedCode = loadGeneratedSource(compilation, "MyDtoBuilder");
+ ProcessorAsserts.assertGenerationSucceeded(compilation, "MyDtoBuilder", generatedCode);
+
+ // The builder method is @Deprecated because the field type is deprecated
+ ProcessorAsserts.assertingResult(
+ generatedCode,
+ contains(
+ """
+ @Deprecated
+ public MyDtoBuilder value(OldType value) {"""));
+
+ // Class-level @SuppressWarnings is present because the builder internally uses the
+ // deprecated type (e.g. TrackedValue, from-instance constructor)
+ ProcessorAsserts.assertingResult(
+ generatedCode,
+ contains(
+ """
+ @SuppressWarnings({"deprecation", "removal"})
+ public class MyDtoBuilder implements IBuilderBase"""));
+ }
+
+ @Test
+ void annotations_deprecatedElementBuilder_classLevelSuppressed() {
+ String packageName = "test.deprecated.elementbuilder";
+
+ // The element type has its own @SimpleBuilder, and the element type is @Deprecated.
+ // The generated collection helper in MyDtoBuilder calls ItemForDeprecationBuilder.create()
+ // internally, which produces deprecation warnings — so MyDtoBuilder needs
+ // class-level @SuppressWarnings.
+ JavaFileObject dto =
+ JavaFileObjects.forSourceString(
+ packageName + ".MyDto",
+ """
+ package test.deprecated.elementbuilder;
+ import java.util.List;
+ import org.javahelpers.simple.builders.core.annotations.SimpleBuilder;
+
+ @SimpleBuilder
+ public class MyDto {
+ private List items;
+
+ public List getItems() { return items; }
+
+ public void setItems(List items) {
+ this.items = items;
+ }
+ }
+ """);
+
+ JavaFileObject itemDto =
+ JavaFileObjects.forSourceString(
+ packageName + ".ItemForDeprecation",
+ """
+ package test.deprecated.elementbuilder;
+ import org.javahelpers.simple.builders.core.annotations.SimpleBuilder;
+
+ @Deprecated
+ @SimpleBuilder
+ public class ItemForDeprecation {
+ private String name;
+
+ public String getName() { return name; }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+ }
+ """);
+
+ Compilation compilation = compileSources(dto, itemDto);
+ String generatedCode = loadGeneratedSource(compilation, "MyDtoBuilder");
+ ProcessorAsserts.assertGenerationSucceeded(compilation, "MyDtoBuilder", generatedCode);
+
+ // Class-level @SuppressWarnings is present because the builder internally calls
+ // ItemForDeprecationBuilder.create() which is @Deprecated
+ ProcessorAsserts.assertingResult(
+ generatedCode,
+ contains(
+ """
+ @SuppressWarnings({"deprecation", "removal"})
+ public class MyDtoBuilder implements IBuilderBase"""));
}
@Test
diff --git a/processor/src/test/java/org/javahelpers/simple/builders/processor/JavaLangAnalyserTest.java b/processor/src/test/java/org/javahelpers/simple/builders/processor/JavaLangAnalyserTest.java
index ffdd15fa..c038b139 100644
--- a/processor/src/test/java/org/javahelpers/simple/builders/processor/JavaLangAnalyserTest.java
+++ b/processor/src/test/java/org/javahelpers/simple/builders/processor/JavaLangAnalyserTest.java
@@ -217,4 +217,82 @@ void findGetterForField_shouldReturnEmpty_whenFieldNameIsNull() {
void findGetterForField_shouldReturnEmpty_whenFieldTypeMirrorIsNull() {
assertTrue(JavaLangAnalyser.findGetterForField(null, "fieldName", null, null).isEmpty());
}
+
+ @Test
+ void extractDeprecatedJavaDoc_shouldReturnNull_whenJavaDocHasNoDeprecatedTag() {
+ String javaDoc =
+ """
+ /**
+ * Some method description.
+ * @param name the name
+ * @return something
+ */
+ """;
+ assertNull(JavaLangAnalyser.extractDeprecatedJavaDoc(javaDoc));
+ }
+
+ @Test
+ void extractDeprecatedJavaDoc_shouldReturnNull_whenJavaDocIsNull() {
+ assertNull(JavaLangAnalyser.extractDeprecatedJavaDoc(null));
+ }
+
+ @Test
+ void extractDeprecatedJavaDoc_shouldExtractSingleLineDeprecatedText() {
+ String javaDoc =
+ """
+ /**
+ * Some method description.
+ * @deprecated use {@link #newMethod} instead
+ * @return something
+ */
+ """;
+ assertEquals(
+ "use {@link #newMethod} instead", JavaLangAnalyser.extractDeprecatedJavaDoc(javaDoc));
+ }
+
+ @Test
+ void extractDeprecatedJavaDoc_shouldExtractMultiLineDeprecatedText() {
+ String javaDoc =
+ """
+ /**
+ * Some method description.
+ * @deprecated use {@link #newMethod} instead
+ * and migrate all callers
+ * @return something
+ */
+ """;
+ assertEquals(
+ "use {@link #newMethod} instead and migrate all callers",
+ JavaLangAnalyser.extractDeprecatedJavaDoc(javaDoc));
+ }
+
+ @Test
+ void findFieldElement_shouldReturnEmpty_whenClassElementIsNull() {
+ assertTrue(JavaLangAnalyser.findFieldElement(null, "fieldName").isEmpty());
+ }
+
+ @Test
+ void findFieldElement_shouldReturnEmpty_whenFieldNameIsNull() {
+ assertTrue(JavaLangAnalyser.findFieldElement(null, null).isEmpty());
+ }
+
+ @Test
+ void findRecordComponent_shouldReturnEmpty_whenTypeElementIsNull() {
+ assertTrue(JavaLangAnalyser.findRecordComponent(null, "componentName").isEmpty());
+ }
+
+ @Test
+ void findRecordComponent_shouldReturnEmpty_whenComponentNameIsNull() {
+ assertTrue(JavaLangAnalyser.findRecordComponent(null, null).isEmpty());
+ }
+
+ @Test
+ void findSetterForField_shouldReturnEmpty_whenDtoTypeIsNull() {
+ assertTrue(JavaLangAnalyser.findSetterForField(null, "fieldName", null).isEmpty());
+ }
+
+ @Test
+ void findSetterForField_shouldReturnEmpty_whenFieldNameIsNull() {
+ assertTrue(JavaLangAnalyser.findSetterForField(null, null, null).isEmpty());
+ }
}