# Java Clean Functions

> Clean methods in Java

- Skill: `caslubbers/java-clean-functions` (Agent Skill)
- Install (CLI): `npx skillmds@latest add caslubbers/java-clean-functions`
- Raw SKILL.md: https://api.skillmd.com/api/skills/caslubbers/java-clean-functions/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: CasLubbers (https://skillmd.com/u/caslubbers)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/caslubbers/java-clean-functions

---


# 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.

