Java Refactoring
Purpose
Behaviour preservation is a claim, and claims need evidence. This skill exists to prevent the two ways "refactoring" goes wrong: the rewrite wearing refactoring's name — no tests, big steps, behaviour quietly changed — and the refactoring that compiles everywhere but breaks clients, because a step crossed a binary- or source-compatibility line nobody checked.
Workflow
The catalogue is authored for Java 25; inspect the target release/toolchain, preview policy, resolved framework versions and CI/runtime before using a version-sensitive technique. Java 21 supports pattern switches but not the final Java 25 flexible-constructor-body feature; do not upgrade or enable preview to make a refactoring example fit. Snippets elide imports, enclosing classes and domain helpers; they are illustrations rather than standalone compilation units.
- Establish and record the baseline. Run the affected tests. They should be green;
if unrelated failures already exist, record them precisely and require the
same baseline after each step rather than claiming an all-green suite. If the changed path
has no meaningful coverage, write
characterisation tests first — read
references/safety-workflow.md, which includes a worked example. No net, no refactoring. The one exception is the step that makes the net possible at all: when the class cannot be constructed or the method cannot be reached, breaking that dependency is done without tests, under the constraints injava-legacy-code-testing. - Classify the boundary. Private or package scope lowers source-compatibility risk, but
does not remove concurrency, reflection, persistence or serialization contracts. If a
framework reaches the name at runtime (JPA field access, Jackson, JPQL, reflective config), in
which case it is case 4 of
references/compatibility.mdwhatever the modifier says. Public within the codebase: every caller moves in the same change. Exported from a module or published to external clients: readreferences/compatibility.mdbefore touching any signature — some steps must stop or become deprecation cycles. - Classify the risk and name the dimensions at stake. Read
references/behaviour-preservation.mdand decide, before the first step, which observable dimensions this step can touch — exception type, side-effect order, transaction boundary, emitted SQL or events, iteration order, memory visibility — and which proof each of those dimensions demands. Selecting the dimensions is what makes step 5's "run tests" mean something. - Choose the technique from the catalogue, routed by what is being reshaped:
references/techniques.mdfor the core moves and the design choices,references/catalogue-statements-and-data.mdfor statements, loops and locals,references/catalogue-conditionals.mdfor branching,references/catalogue-api-shape.mdfor signatures,references/catalogue-inheritance.mdfor hierarchies. Every entry in the four catalogue files carries a labelled precondition — check it before the step, not after. - Take one mechanical step: transform, compile, test, inspect the diff. Commit only when explicitly requested; the small-step discipline also applies to uncommitted work. Where an IDE refactoring is available, use it: it resolves references the compiler will not report. Editing by hand — which is the agent's case — the substitute is the compiler plus an explicit caller enumeration: make the old symbol inaccessible and compile, then search the old name as a string across resources, XML, JPQL and annotations. Grep alone is not the enumeration. Automating the step across many files is refactoring-automation's.
- Repeat until done, then re-run the detection pass (java-code-smells) to confirm the finding that motivated the work is actually gone.
Rules
- A refactoring commit contains no behaviour change. A bug discovered mid-refactoring is recorded and fixed in its own commit, before or after — never inside.
- Each commit is coherent, buildable and revertible in reverse order. If rollback needs an unrelated semantic repair or data recovery, the step crossed more than a code-refactoring boundary.
- A new failure after a step is diagnosed against the recorded baseline. Revert when the step caused it; do not patch production or dismiss a flaky/external failure without evidence.
- Do not weaken a contract assertion merely to get green. Implementation-coupled assertions may need mechanical updates while externally observable behavior stays fixed; explain why the assertion was not part of the contract and retain stronger outcome evidence.
- Renaming or reshaping anything exported, published, persisted or serialized crosses an evolution boundary. Java signatures route to java-api-design, wire schemas to rpc-and-api-contracts/schema-evolution-and-compatibility, and native Java serialization to java-serialization-hardening.
- Do not justify a refactoring by performance without a measurement. Restructuring changes allocation and dispatch patterns in both directions; claim readability, or bring a benchmark.
References
- Technique catalogue — the core moves (Extract/Inline, Move, Rename, Parameter Object, Replace Type Code, Encapsulate Collection) and the design choices between them. Read when choosing or executing a step.
- Statements, loops and data — Slide Statements, Split/Combine Loops, Split Phase, Split Variable, Replace Temp with Query, Replace Derived Variable with Query, reference↔value. Read when a method resists extraction, or before reordering anything.
- Conditional logic — Decompose Conditional, guard clauses, Consolidate, Introduce Special Case, Introduce Assertion, instanceof chain to pattern switch. Read before inverting any condition or otherwise changing branching.
- Reshaping a signature — Change Function Declaration, Encapsulate Variable, Separate Query from Modifier, Remove Flag Argument, Preserve Whole Object, Remove Setting Method, Replace Constructor with Factory. Read when the change is visible to callers.
- Moving members through a hierarchy — Pull Up
and Push Down, Extract Superclass, Collapse Hierarchy, Replace Subclass or Superclass
with Delegate. Read before touching any
extends, or before creating one. - Behaviour preservation — the dimensions of observable behaviour, risk classification, the places the compiler and the tests both lie, and the evidence ladder. Read at step 3 — it is what produces the classification.
- Safety workflow — characterisation tests end to end, with a worked example that pins a bug on purpose. Read whenever coverage is missing or untrusted.
- Compatibility — which changes break binary, source or behavioural compatibility, and where a refactoring must stop. Read before any step that touches a public or exported signature.