Menu
Module 4 / 8 Intermediate 25 min read

Everyday Java Tasks

The bread-and-butter jobs — understanding legacy code, writing tests, fixing bugs, and small refactors — done with an agent.

In this module

Apply Claude Code to the daily work of a Java developer: exploring unfamiliar code, generating tests, debugging from stack traces, and safe refactors.

The work that actually fills your day

Most Java work isn’t greenfield. It’s understanding a service someone left three years ago, adding a test to code that has none, chasing a NullPointerException from a production log, and renaming a concept that leaked into forty files. These are exactly the tasks where an agent earns its keep. This module is a set of recipes.

Recipe: understand unfamiliar code

You’ve been handed a module you’ve never seen. Instead of spelunking through it manually, have Claude build you a map.

> I'm new to the billing module. Explain how an invoice gets created,
  from the inbound event to the database write. List the key classes
  in order, and call out anything surprising or risky.

Then go deeper where it matters:

> Draw the relationship between InvoiceService, the JPA entities, and
  the outbox table. Where are transactions opened and committed?

Claude reads the actual code, so the explanation reflects this codebase, not a generic pattern. For onboarding, it turns hours of digging into a map you can read in minutes — one you still verify against the code before acting on it, especially around transaction boundaries and concurrency, where a confident-sounding summary can be subtly wrong.

Recipe: generate tests for untested code

Legacy Java is full of untested branches. Claude is excellent here because it can run the test and confirm it actually passes.

> PriceCalculator has no tests. Read it, then write JUnit 5 tests that
  cover the tax brackets, the rounding rules, and the zero-quantity
  edge case. Match the AssertJ style used in the existing tests under
  src/test. Run them and confirm green.

Push for the cases humans skip:

> Now add tests for the failure paths — negative prices and a null
  currency — and assert the exact exception type and message.

Watch for tautological tests. An agent eager to make tests pass can write assertions that merely restate the implementation, or mock so heavily that nothing real is exercised. Read the assertions. A test that can’t fail when the logic breaks is worse than no test, because it lends false confidence. You’ll make this discipline systematic in Module 5.

Recipe: debug from a stack trace

This is one of the highest-leverage uses. Paste the whole stack trace — Claude reads Java traces fluently and walks to the cause.

> This is failing in production:
!cat /tmp/incident-4821.log
  Find the root cause. Don't fix anything yet — explain what's
  happening and where, then propose the smallest safe fix.

Asking it to explain before fixing is deliberate: you want to confirm the diagnosis before any code changes, the same way you’d want a colleague to convince you before they touch the hot path. Once you agree:

> Good. Apply that fix and add a regression test that fails without it.

The regression test is the part people skip. Insisting on a test that fails before the fix and passes after is how you know the fix is real — and how you stop the bug returning.

Recipe: a safe, surgical refactor

IntelliJ’s rename is exact but mechanical; it can’t restructure intent. Claude can do conceptual refactors — but you keep it safe by working against a green test suite.

> Rename the concept "client" to "customer" across the orders module —
  classes, fields, JSON properties, and the database column (with a
  Liquibase changeset). Keep the public REST contract unchanged via
  @JsonProperty. Run the full module test suite after.

The guardrail is the suite. If the tests are green before and green after, the refactor preserved behaviour. When the area is under-tested, do the boring thing first:

> Before refactoring, add characterization tests that pin down the
  current behaviour of OrderAssembler. Then we refactor against them.

A refactor is only as safe as the tests it runs against. With an agent, "write the tests first" stops being a virtue you aspire to and becomes the mechanism that lets you move fast.

Recipe: the small stuff, batched

Claude shines at tedium that’s beneath a dedicated tool but above worth-doing-by-hand:

> Replace every System.out.println in the service layer with SLF4J
  logging at the appropriate level, add the logger field where
  missing, and make sure nothing logs secrets.

> Add Javadoc to every public method in the api package, describing
  parameters and thrown exceptions. Keep it factual — no filler.

> We're on Java 21 now. Where it genuinely improves clarity, modernise
  this package: records for the obvious data carriers, pattern matching
  in switch with record patterns to replace the instanceof chains, and
  leave anything ambiguous alone.

The phrase “leave anything ambiguous alone” matters: it tells the agent to stop at the boundary of its confidence rather than forcing every case.

When it goes off the rails

It will, sometimes. Knowing the failure modes and the recovery moves keeps a bad turn from becoming a bad commit:

  • It edits the wrong thing or over-reaches. Hit Esc to interrupt mid-turn, say what was wrong in one sentence, and let it continue. You don’t have to wait for a turn to finish.
  • It “fixes” a failing test by weakening or deleting it. This is the one to watch hardest. If a test went green suspiciously fast, read the diff to the test, not just the code.
  • It loops — fix, break, re-fix. When you see the same error twice, stop it and look yourself; feeding it the same context again rarely helps. Often the real problem is one it can’t see (a stale build, an environment issue, a wrong assumption in CLAUDE.md).
  • It invents an API or a dependency. A hallucinated method or an unfamiliar Maven coordinate shows up as a compile error — your verifier catches it. Don’t add a dependency you haven’t checked is real and maintained.

Your safety net for all of these is git: work on a branch (see Module 8), and when a diff is beyond saving, git restore . and restate the task with a cleaner prompt. Throwing away a bad attempt and re-prompting is usually faster than negotiating a tangled one back to correctness.

When to keep the work yourself

An agent is not the right tool for everything. Keep your hands on:

  • Decisions with no cheap verifier — choosing a concurrency model, an API contract other teams depend on, a security boundary. Claude can lay out options; the call is yours.
  • Things you don’t yet understand. Don’t ship code you can’t review. If a diff is beyond your ability to evaluate, slow down and learn it — or you’ve just merged a liability.
  • The truly trivial. A one-line fix you can make in IntelliJ in five seconds doesn’t need a prompt.

What just happened

You now have repeatable recipes for the daily grind: mapping unfamiliar code, generating real tests, debugging from traces, and refactoring against a safety net. The connective tissue is verification, which the next module turns into a deliberate workflow.

Key takeaways

  • Have Claude map unfamiliar modules from real code, but verify claims about transactions and concurrency.
  • Generate tests including failure paths — and read the assertions to reject tautological or over-mocked tests.
  • Debug by pasting the full stack trace, asking for diagnosis before fix, then demanding a regression test that fails without the fix.
  • Refactor against a green suite; add characterization tests first where coverage is thin.
  • Keep judgment calls, unreviewable diffs, and trivial edits for yourself.