From lombok-remover
Scan a Java project and replace all Lombok annotations with equivalent hand-written Java code. Use this skill whenever the user wants to remove Lombok, de-lombok a project, replace Lombok annotations, migrate away from Lombok, or eliminate the Lombok dependency. Also trigger when the user says things like "get rid of Lombok", "expand Lombok", or "make Lombok explicit". Handles all common Lombok annotations (@Data, @Builder, @Value, @Getter, @Setter, @ToString, @EqualsAndHashCode, @RequiredArgsConstructor, @AllArgsConstructor, @NoArgsConstructor, @Slf4j, @With, @Accessors, etc.), updates POM files to remove the Lombok dependency, and preserves existing code style throughout.
How this skill is triggered — by the user, by Claude, or both
Slash command
/lombok-remover:lombok-removerThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Replaces every Lombok annotation in a Java project with idiomatic, hand-written Java code,
Replaces every Lombok annotation in a Java project with idiomatic, hand-written Java code, then removes the Lombok dependency from all POM files.
find . -name "*.java" | xargs grep -l "import lombok\." | sort
find . -name "pom.xml" | sort
Collect the full list of affected Java files and POM files upfront.
For every affected Java file, read it fully and build a mental model of:
final/non-final, nullability annotations@Accessors(chain = true) or @Accessors(prefix = ...) modifiers@Builder.Default field defaults@Singular collections in buildersApply the rules in the Annotation Reference section below. Generate complete, correctly indented Java for each annotation removed. Match the file's existing indentation style (spaces vs tabs, indent width).
Before editing any file, print a concise summary:
Files to modify: 12
─────────────────────────────────────────────────
File Annotations removed
─────────────────────────────────────────────────
src/.../UserService.java @Slf4j
src/.../User.java @Data, @Builder
src/.../AddressDto.java @Value
...
─────────────────────────────────────────────────
POM files: 2 (lombok dependency will be removed)
Ask the user to confirm before proceeding. If they say yes, apply all changes.
For each file:
Remove all import lombok.* and specific import lombok.XYZ statements
Remove all Lombok annotations from the class / fields
Insert generated methods at the end of the class body, before the closing },
unless hand-written methods already exist at the bottom (in which case insert before them)
For getters and setters, also provide Javadoc according to the style of the project being updated.
Do not reformat unrelated code
In each pom.xml, remove the <dependency> block for org.projectlombok:lombok and
any associated <annotationProcessorPaths> entry for Lombok in the
maven-compiler-plugin configuration.
✅ Done
──────────────────────────────────────────────────────────────
Modified 12 Java files, 2 POM files
Removed annotations: @Data ×4, @Builder ×3, @Value ×2, @Slf4j ×6, @Getter ×1
──────────────────────────────────────────────────────────────
Next steps:
• Run `mvn compile` to verify the project builds cleanly.
• Review generated equals/hashCode if the class participates in collections or JPA.
@Getter / @SetterOn a field:
// @Getter on field →
public FieldType getFieldName() { return this.fieldName; }
// @Setter on field →
public void setFieldName(final FieldType fieldName) { this.fieldName = fieldName; }
On a class: generate getter (and setter for non-final) for every non-static field.
Boolean fields: use isX() instead of getX() when the type is boolean (primitive).
Access levels: @Getter(AccessLevel.PROTECTED) etc. → use the matching Java modifier.
AccessLevel.NONE → skip that field entirely.
Provide Javadoc for inserted getters and setters according to the style of the project being updated.
@ToString@Override
public String toString() {
return "ClassName{" +
"field1=" + field1 +
", field2='" + field2 + '\'' +
'}';
}
@ToString(exclude = {"field2"}) / @ToString.Exclude → omit those fields@ToString(onlyExplicitlyIncluded = true) + @ToString.Include → include only marked fields@ToString(callSuper = true) → prepend super.toString() + ", "Quote String fields with ' as shown; for arrays use Arrays.toString(field).
@EqualsAndHashCode@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ClassName other = (ClassName) o;
return Objects.equals(field1, other.field1) &&
Objects.equals(field2, other.field2);
}
@Override
public int hashCode() {
return Objects.hash(field1, field2);
}
@EqualsAndHashCode(exclude = {...}) / @EqualsAndHashCode.Exclude → omit those fields@EqualsAndHashCode(onlyExplicitlyIncluded = true) + @EqualsAndHashCode.Include → only marked@EqualsAndHashCode(callSuper = true) → chain to super.equals() / super.hashCode()== instead of Objects.equalsAdd import java.util.Objects; if not already present.
@NoArgsConstructorpublic ClassName() {
}
@NoArgsConstructor(access = AccessLevel.PROTECTED) → use protected.
@NoArgsConstructor(force = true) → initialize final fields to their default zero values.
@AllArgsConstructorpublic ClassName(Type1 field1, Type2 field2) {
this.field1 = field1;
this.field2 = field2;
}
Include all non-static fields in declaration order. Respect access attribute.
@RequiredArgsConstructorGenerate a constructor for all final fields and all fields annotated @NonNull
(that are not initialized at declaration). Same body pattern as @AllArgsConstructor.
@DataEquivalent to: @Getter + @Setter (non-final fields) + @ToString + @EqualsAndHashCode + @RequiredArgsConstructor.
Generate all of those in this order: constructor, getters, setters, equals, hashCode, toString.
@ValueEquivalent to: make class final, make all fields private final, @Getter + @ToString + @EqualsAndHashCode + @AllArgsConstructor.
No setters. The class declaration gains final if not already present.
@BuilderGenerate a static inner Builder class:
public static Builder builder() {
return new Builder();
}
public static final class Builder {
private Type1 field1;
private Type2 field2;
private Builder() {}
public Builder field1(Type1 field1) {
this.field1 = field1;
return this;
}
public Builder field2(Type2 field2) {
this.field2 = field2;
return this;
}
public ClassName build() {
return new ClassName(field1, field2);
}
}
// Private all-args constructor used by Builder:
private ClassName(Type1 field1, Type2 field2) {
this.field1 = field1;
this.field2 = field2;
}
@Builder.Default: the field's declared initializer becomes the default in the Builder
field declaration (e.g. private List<String> tags = new ArrayList<>();).
@Singular: generate an add method and a addAll method, plus initialize the field
to an empty mutable list/set/map in the Builder, and make the build() call
Collections.unmodifiableList(new ArrayList<>(tags)).
@Builder(toBuilder = true): add a toBuilder() instance method that pre-populates
a new Builder from this.
@SuperBuilderSame as @Builder but uses an abstract base builder pattern to support inheritance.
This is complex — generate the abstract BaseBranchBuilder / concrete builder pair
as Lombok does. Flag it clearly in the change log as requiring extra review.
@With / @WitherFor each annotated field, generate a copy-constructor-style "wither":
public ClassName withFieldName(FieldType fieldName) {
return new ClassName(/* all fields, replacing fieldName */);
}
Requires an all-args constructor to be present (or generated).
@Slf4j / @Log / @Log4j2 / @CommonsLog| Annotation | Generated field |
|---|---|
@Slf4j | private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ClassName.class); |
@Log4j2 | private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(ClassName.class); |
@Log | private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(ClassName.class.getName()); |
@CommonsLog | private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(ClassName.class); |
Insert as the first field in the class body. Add the corresponding import.
@Accessorschain = true → setters return this instead of voidprefix = {"m_"} → strip the prefix when computing getter/setter namesfluent = true → getters and setters use the bare field name with no get/set prefix,
and setters return thisApply these modifiers to every getter/setter generated for this class.
@NonNullOn a parameter or field: insert a null check at the top of the constructor/setter body:
if (fieldName == null) throw new NullPointerException("fieldName is marked non-null but is null");
Keep any JSR-305 / JetBrains / Jakarta @NonNull annotations that are used for static
analysis — only remove the lombok.NonNull import/annotation.
@Cleanup// @Cleanup InputStream in = new FileInputStream(file);
// becomes:
InputStream in = new FileInputStream(file);
try {
// ... rest of the original block ...
} finally {
if (in != null) in.close();
}
@SneakyThrowsWrap the method body in a try-catch that rethrows via an unchecked wrapper:
try {
// original body
} catch (Exception e) {
throw new RuntimeException(e);
}
Add // TODO: review exception handling (was @SneakyThrows) comment.
@DelegateImplement every method of the delegated type by forwarding to the delegate field.
This may generate many methods — note the count in the change log and add a
// Generated by lombok-remover: @Delegate comment block.
callSuper should
default to true for @EqualsAndHashCode / @ToString. Lombok's default is false
— preserve that unless callSuper = true was explicitly set.// TODO: review equals/hashCode for JPA entity comment when
the class has @Entity or @Table.@Getter on an
abstract class generates abstract getters only if the field is abstract (unusual).lombok.config): check if a lombok.config file overrides defaults
(e.g. lombok.addLombokGeneratedAnnotation, lombok.accessors.chain). Read it if present.Remove:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
...
</dependency>
And if present in maven-compiler-plugin:
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</path>
</annotationProcessorPaths>
If the <annotationProcessorPaths> block becomes empty after removal, remove the block
too. If <compilerArgs> only contained Lombok-related args, clean those up as well.
When generating code, ensure all required imports are present and no unused Lombok imports remain. Common imports to add:
java.util.Objects — for equals / hashCodejava.util.Arrays — for array fields in toStringjava.util.Collections — for @Singular builder fieldsnpx claudepluginhub litsec/claude-code-skills --plugin lombok-removerAssists with Maven for Java: initializes projects, configures pom.xml, manages dependencies and scopes, sets up builds/plugins/profiles, troubleshoots errors.
Detects Java version from pom.xml or build.gradle and suggests refactorings like lambdas (8+), streams (8+), records (16+), pattern matching (17+), plus universal changes like method extraction and dead code removal.
Manages Maven dependencies in Java projects: declares with scopes, resolves version conflicts and transitives, configures BOMs, and optimizes dependency trees.