Enforces method design in Java 21+ — small methods that do one thing, at most three parameters, no boolean flag arguments, no null returns or arguments, and command-query separation. Use when writing or refactoring Java methods and constructors, and when the code shows long methods, parameter lists of four or more, boolean flags, deep nesting, output parameters, or methods returning null. Also trigger on: private helpers placed above the public methods that call them, a class that must be rea...
Scanned 9/19/2026
npx -y skills add CasLubbers/code-design-skills --skill java-clean-functions --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Java Clean Functions?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/caslubbers-java-clean-functions)More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.
---
name: java-clean-functions
description: Enforces method design in Java 21+ — small methods that do one thing, at most three parameters, no boolean flag arguments, no null returns or arguments, and command-query separation. Use when writing or refactoring Java methods and constructors, and when the code shows long methods, parameter lists of four or more, boolean flags, deep nesting, output parameters, or methods returning null. Also trigger on: private helpers placed above the public methods that call them, a class that must be read bottom-up, a method mixing orchestration with low-level detail, or asks about the stepdown rule, "should this read like a book", "what order should these methods go in".
---
# Clean methods in Java
## One thing, one level of abstraction
A method should read as a single idea. Mixed levels — orchestration next to byte fiddling — is the clearest sign it is doing two jobs.
```java
// Bad — orchestration, HTTP, parsing, and persistence in one place
public void syncOrders() {
var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder(URI.create(url)).build();
var response = client.send(request, BodyHandlers.ofString());
var node = mapper.readTree(response.body());
for (var item : node.get("orders")) {
var order = new Order(item.get("id").asText(), item.get("total").asDouble());
jdbc.update("INSERT INTO orders ...", order.id(), order.total());
}
}
// Good — each step is nameable and testable
public void syncOrders() {
var payload = fetchOrders();
var orders = parseOrders(payload);
orderRepository.saveAll(orders);
}
```
## The stepdown rule: the class reads top-down
A class should read like a narrative. Public API first, then the private methods it calls, in call
order — the reader descends one level of abstraction at a time and stops when they know enough.
```java
// Good — the story, then the details underneath it
public final class OrderExporter {
public String export(List<Order> orders) {
return orders.stream().map(this::toRow).collect(joining("\n"));
}
private String toRow(Order order) {
return order.fields().stream().map(this::escapeQuotes).collect(joining(","));
}
private String escapeQuotes(String value) {
return value.replace("\"", "\"\"");
}
}
```
Fields at the top, constructors next, then public methods, then the private helpers each one calls.
A private helper used by two public methods goes below both.
This is why one-level-of-abstraction matters practically: a method mixing orchestration with
character-level detail cannot be placed in the ordering, because it belongs at two levels at once.
That is the signal to split it. If a whole class resists the ordering, it has more than one
responsibility.
## Three parameters, then stop
```java
// Bad
public Reservation book(String guest, LocalDate from, LocalDate to,
int guests, boolean breakfast, String notes) { ... }
// Good — the arguments were an object all along
public Reservation book(BookingRequest request) { ... }
public record BookingRequest(
String guest, DateRange stay, int guests, boolean breakfast, String notes) {}
```
Records make parameter objects cheap: one line, immutable, with `equals`, `hashCode`, and `toString` supplied. Grouping parameters that always travel together is not overhead, it is the missing concept.
Constructors with many required fields are the one place a builder still earns its keep — but check first whether the object is doing too much.
## No boolean flags
A flag parameter says the method has two behaviours.
```java
// Bad — the call site is unreadable: report(data, true, false)
public Report generate(Data data, boolean detailed, boolean includeArchived)
// Good
public Report generateSummary(Data data)
public Report generateDetailed(Data data)
```
If the flag genuinely selects a mode, an enum names it: `generate(data, Detail.FULL)`.
## Guard clauses over nesting
```java
// Bad
public void process(Order order) {
if (order != null) {
if (order.isValid()) {
if (!order.isProcessed()) {
doWork(order);
}
}
}
}
// Good
public void process(Order order) {
if (!order.isValid()) {
throw new IllegalArgumentException("invalid order: " + order.id());
}
if (order.isProcessed()) {
return;
}
doWork(order);
}
```
Handle the exceptional path first and return; keep the real work at one indentation level.
## Never return null
```java
// Bad — every caller must remember to check
public User findUser(String id) { return null; }
// Good
public Optional<User> findUser(String id) { ... }
public List<Order> ordersFor(String id) { return List.of(); } // empty, not null
```
`Optional` belongs on return types. Do not use it for fields or parameters — an overload or a required argument is clearer. And do not accept `null` arguments as a design: an overload beats a nullable parameter.
## Command-query separation
A method either returns a value or changes state, not both.
```java
// Bad — did it set something, or ask something?
if (setAttribute("username", "alice")) { ... }
// Good
if (hasAttribute("username")) {
setAttribute("username", "alice");
}
```
Fluent builders returning `this` are the accepted exception.
## No output parameters
Mutating an argument to communicate a result hides the effect from the signature.
```java
// Bad
public void appendFooter(StringBuilder report)
// Good
public String withFooter(String report)
```
Prefer returning a new value. Records and `List.copyOf` make immutable results cheap.
## Prefer exceptions to error codes, and be specific
Throw a meaningful exception rather than returning a status the caller can ignore. Unchecked exceptions for programmer error and unrecoverable conditions; a checked exception only when the caller has a genuine recovery path. Never swallow one:
```java
// Bad
try { risky(); } catch (Exception e) { }
// Good
try {
risky();
} catch (IOException e) {
throw new SyncFailedException("sync orders from " + url, e); // cause preserved
}
```
## Delete dead methods
Unused private methods, methods kept "in case", and code reachable only from a deleted feature all go. Version control remembers them.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!