Lombok remover
Skill litsec/claude-code-skills/plugins/lombok-remover/skills/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.From its SKILL.md
npx -y skills add litsec/claude-code-skills --skill lombok-removerAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
13.0 KB, ~2.9k tokens by cl100k_base, as published. Nobody here has run it
Lombok Remover
Replaces every Lombok annotation in a Java project with idiomatic, hand-written Java code, then removes the Lombok dependency from all POM files.
Workflow
1. Discover scope
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.
2. Analyse each file before touching it
For every affected Java file, read it fully and build a mental model of:
- Which Lombok annotations are present (may be multiple per class)
- Which fields exist, their types,
final/non-final, nullability annotations - Whether the class already has any hand-written methods that would conflict
- Any
@Accessors(chain = true)or@Accessors(prefix = ...)modifiers - Any
@Builder.Defaultfield defaults - Any
@Singularcollections in builders - Whether the class is a record candidate (all-final fields, no mutation) — note it but do NOT convert to a record unless the user explicitly asks
3. Generate replacements
Apply 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).
4. Show a plan, then apply
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.
5. Edit files
For each file:
-
Remove all
import lombok.*and specificimport lombok.XYZstatements -
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
6. Update POMs
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.
7. Print a final change log
✅ 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.
Annotation Reference
@Getter / @Setter
On 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)→ prependsuper.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 tosuper.equals()/super.hashCode()- Primitive fields: use
==instead ofObjects.equals
Add import java.util.Objects; if not already present.
@NoArgsConstructor
public ClassName() {
}
@NoArgsConstructor(access = AccessLevel.PROTECTED) → use protected.
@NoArgsConstructor(force = true) → initialize final fields to their default zero values.
@AllArgsConstructor
public ClassName(Type1 field1, Type2 field2) {
this.field1 = field1;
this.field2 = field2;
}
Include all non-static fields in declaration order. Respect access attribute.
@RequiredArgsConstructor
Generate a constructor for all final fields and all fields annotated @NonNull
(that are not initialized at declaration). Same body pattern as @AllArgsConstructor.
@Data
Equivalent to: @Getter + @Setter (non-final fields) + @ToString + @EqualsAndHashCode + @RequiredArgsConstructor.
Generate all of those in this order: constructor, getters, setters, equals, hashCode, toString.
@Value
Equivalent to: make class final, make all fields private final, @Getter + @ToString + @EqualsAndHashCode + @AllArgsConstructor.
No setters. The class declaration gains final if not already present.
@Builder
Generate 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.
@SuperBuilder
Same 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 / @Wither
For 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.
@Accessors
chain = true→ setters returnthisinstead ofvoidprefix = {"m_"}→ strip the prefix when computing getter/setter namesfluent = true→ getters and setters use the bare field name with noget/setprefix, and setters returnthis
Apply these modifiers to every getter/setter generated for this class.
@NonNull
On 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();
}
@SneakyThrows
Wrap 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.
@Delegate
Implement 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.
Edge Cases
- Existing hand-written methods: if a getter/setter/toString/equals/hashCode already exists, skip generating that one and log a note.
- Inheritance: if the class extends another class, check whether
callSupershould default totruefor@EqualsAndHashCode/@ToString. Lombok's default isfalse— preserve that unlesscallSuper = truewas explicitly set. - JPA entities: add a
// TODO: review equals/hashCode for JPA entitycomment when the class has@Entityor@Table. - Records: do not convert to records unless asked.
- Interfaces / abstract classes: handle appropriately — e.g.
@Getteron an abstract class generates abstract getters only if the field is abstract (unusual). - Lombok config (
lombok.config): check if alombok.configfile overrides defaults (e.g.lombok.addLombokGeneratedAnnotation,lombok.accessors.chain). Read it if present.
POM Cleanup
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.
Required Imports
When generating code, ensure all required imports are present and no unused Lombok imports remain. Common imports to add:
java.util.Objects— forequals/hashCodejava.util.Arrays— for array fields intoStringjava.util.Collections— for@Singularbuilder fields- Logger class imports — for log field generation
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.