Debugging
Purpose
Turn a report into a cause. The alternative — changing things until the symptom disappears —
produces code nobody understands, a bug that returns under a slightly different input, and no
way to tell whether the change helped or moved the failure somewhere quieter.
The discipline is cheap and it is nearly always skipped under pressure, which is exactly when
guessing is most expensive.
Workflow
- Restate the fault as an observation. "Customer 88123 saw a negative balance at 14:02"
is an observation. "The refund logic is broken" is a hypothesis wearing a report's clothes,
and adopting it early is how the wrong subsystem gets investigated for a day.
- Reproduce it. Deterministically if possible, intermittently if not — but know which,
because "I cannot reproduce it" and "it reproduces one time in ten" lead to different work.
If it only happens in production, collect evidence within the mitigation budget
(
references/production-evidence.md).
- Shrink. Remove inputs, steps, data and configuration until removing anything more makes
the fault disappear, while preserving the original failure signature. Stop when the next
reduction costs more than it helps; a minimal reproduction need not identify the full cause.
- State a hypothesis that predicts something you have not yet looked at. "If the cause is
the missing time zone, then the row written at 23:30 local will carry yesterday's date."
Also name an observation that would refute it. Explaining existing evidence is useful;
a discriminating prediction makes the next experiment useful.
- Test it by changing exactly one thing, and record the result whether it confirms or
refutes. Two changes at once means a confirmed hypothesis is still ambiguous.
- Check the explanation against the distribution — frequency, timing, affected and unaffected
users. Record remaining contradictions and uncertainty instead of forcing one cause to explain
unrelated failures.
- Write the failing test, then fix, then verify (tdd). The reproduction from step 3 is
a candidate test. Use the narrowest level preserving the failure; a race may need controlled
scheduling or integration coverage. Record when reproduction remains intermittent.
Inspect deployed versus source JDK/toolchain, dependencies, JVM flags, configuration and data
versions before version-sensitive diagnostics or changes. This workflow has no universal Java
baseline. Preserve the target environment; upgrading it is a separate decision. Return the fault,
evidence, tested/refuted hypotheses, fix or mitigation, validation and unresolved gaps. Missing
access or reproduction permits an evidence plan, not an invented root cause.
Rules
- Prefer a causal explanation before a permanent fix. During an incident, a reversible mitigation
may precede diagnosis; label it as mitigation, preserve the evidence the response budget allows,
and do not present symptom disappearance as root-cause proof.
- Prefer one controlled variable per experiment. If a batch helps, it implicates the batch but
does not isolate a member or interaction; reduce/revert the batch in a controlled environment.
- Read the whole stack trace, including cause and suppressed chains. The deepest application frame
is a useful boundary candidate, not a verdict: framework callbacks, generated code, reflection,
native frames, and library defects can move or hide the causal frame.
- Balance evidence preservation against customer impact and the incident commander's authority.
Capture cheap, non-disruptive evidence first when the error budget permits; mitigate immediately
when delay is unsafe, and record which volatile evidence the action destroyed
(
references/production-evidence.md).
- Symptom disappearance is evidence of recovery, not proof of cause or durable correction.
- Question the assumption that the fault is where the symptom is. Corrupted state is written
in one place and observed in another, often much later; the write is the bug.
- Consider
git bisect when verified good/bad revisions and a reliable classifier exist.
It can narrow a monotone change in roughly log₂(n) classifications; build cost, skipped commits
and intermittent or nonmonotone outcomes affect that benefit. See the reference's exit protocol.
- Delete the debugging output before the change ships, and if a log line was genuinely useful,
promote it deliberately with a level and structure (structured-logging) rather than leaving
a
System.out.println.
- Timebox according to incident severity and experiment cost. Repeated refutations are a signal
to revisit observations, harness assumptions and the system model or seek another perspective.
References
- The method in detail —
references/method.md. Shrinking a reproduction, differential
diagnosis (what changed — code, data, config, dependency, traffic, time), bisection over
commits and over data, reading exception chains, and the specific traps of intermittent and
heisenbug faults. Read when the fault resists the workflow above.
- Evidence from a running system —
references/production-evidence.md. What each source
can and cannot answer — logs, metrics, traces, thread dumps, heap dumps, JFR, database state,
deployment history — with volatility, cost and collection order during an incident. Read
before touching a production system that is currently faulty.
1---2name: debugging3description: Finding the cause of a fault instead of a change that makes the symptom go away: reproducing before diagnosing, shrinking the reproduction until nothing is removable, stating a hypothesis that predicts an observation, changing one variable at a time, bisecting, and choosing which evidence to collect from a running production system before it is destroyed. Use when a fix is being guessed at, when a change "seems to work", when the same bug keeps coming back, when a fault cannot be reproduced, when a production incident needs a cause rather than a restart, when print statements are being added everywhere, or when several changes were made at once and it now works. Does not cover JVM performance triage (java-performance), GC (jvm-gc-tuning), live thread diagnosis (concurrency-diagnostics), heap dump mechanics (heap-dump-analysis), or deliberately injecting failures (distributed-systems-testing).4---56# Debugging78## Purpose910Turn a report into a cause. The alternative — changing things until the symptom disappears —11produces code nobody understands, a bug that returns under a slightly different input, and no12way to tell whether the change helped or moved the failure somewhere quieter.1314The discipline is cheap and it is nearly always skipped under pressure, which is exactly when15guessing is most expensive.1617## Workflow18191. **Restate the fault as an observation.** "Customer 88123 saw a negative balance at 14:02"20 is an observation. "The refund logic is broken" is a hypothesis wearing a report's clothes,21 and adopting it early is how the wrong subsystem gets investigated for a day.222. **Reproduce it.** Deterministically if possible, intermittently if not — but know which,23 because "I cannot reproduce it" and "it reproduces one time in ten" lead to different work.24 If it only happens in production, collect evidence within the mitigation budget25 (`references/production-evidence.md`).263. **Shrink.** Remove inputs, steps, data and configuration until removing anything more makes27 the fault disappear, while preserving the original failure signature. Stop when the next28 reduction costs more than it helps; a minimal reproduction need not identify the full cause.294. **State a hypothesis that predicts something you have not yet looked at.** "If the cause is30 the missing time zone, then the row written at 23:30 local will carry yesterday's date."31 Also name an observation that would refute it. Explaining existing evidence is useful;32 a discriminating prediction makes the next experiment useful.335. **Test it by changing exactly one thing**, and record the result whether it confirms or34 refutes. Two changes at once means a confirmed hypothesis is still ambiguous.356. **Check the explanation against the distribution** — frequency, timing, affected and unaffected36 users. Record remaining contradictions and uncertainty instead of forcing one cause to explain37 unrelated failures.387. **Write the failing test, then fix, then verify** (tdd). The reproduction from step 3 is39 a candidate test. Use the narrowest level preserving the failure; a race may need controlled40 scheduling or integration coverage. Record when reproduction remains intermittent.4142Inspect deployed versus source JDK/toolchain, dependencies, JVM flags, configuration and data43versions before version-sensitive diagnostics or changes. This workflow has no universal Java44baseline. Preserve the target environment; upgrading it is a separate decision. Return the fault,45evidence, tested/refuted hypotheses, fix or mitigation, validation and unresolved gaps. Missing46access or reproduction permits an evidence plan, not an invented root cause.4748## Rules4950- Prefer a causal explanation before a permanent fix. During an incident, a reversible mitigation51 may precede diagnosis; label it as mitigation, preserve the evidence the response budget allows,52 and do not present symptom disappearance as root-cause proof.53- Prefer one controlled variable per experiment. If a batch helps, it implicates the batch but54 does not isolate a member or interaction; reduce/revert the batch in a controlled environment.55- Read the whole stack trace, including cause and suppressed chains. The deepest application frame56 is a useful boundary candidate, not a verdict: framework callbacks, generated code, reflection,57 native frames, and library defects can move or hide the causal frame.58- Balance evidence preservation against customer impact and the incident commander's authority.59 Capture cheap, non-disruptive evidence first when the error budget permits; mitigate immediately60 when delay is unsafe, and record which volatile evidence the action destroyed61 (`references/production-evidence.md`).62- Symptom disappearance is evidence of recovery, not proof of cause or durable correction.63- Question the assumption that the fault is where the symptom is. Corrupted state is written64 in one place and observed in another, often much later; the write is the bug.65- Consider `git bisect` when verified good/bad revisions and a reliable classifier exist.66 It can narrow a monotone change in roughly log₂(n) classifications; build cost, skipped commits67 and intermittent or nonmonotone outcomes affect that benefit. See the reference's exit protocol.68- Delete the debugging output before the change ships, and if a log line was genuinely useful,69 promote it deliberately with a level and structure (structured-logging) rather than leaving70 a `System.out.println`.71- Timebox according to incident severity and experiment cost. Repeated refutations are a signal72 to revisit observations, harness assumptions and the system model or seek another perspective.7374## References7576- **The method in detail** — `references/method.md`. Shrinking a reproduction, differential77 diagnosis (what changed — code, data, config, dependency, traffic, time), bisection over78 commits and over data, reading exception chains, and the specific traps of intermittent and79 heisenbug faults. Read when the fault resists the workflow above.80- **Evidence from a running system** — `references/production-evidence.md`. What each source81 can and cannot answer — logs, metrics, traces, thread dumps, heap dumps, JFR, database state,82 deployment history — with volatility, cost and collection order during an incident. Read83 before touching a production system that is currently faulty.