Java Errors and Debugging
Learn to explain a failure before changing the code. Separate compiler diagnostics, runtime exceptions, and wrong answers; follow the evidence through small examples, then repair a session report and keep the cases that proved it wrong.
Before you start: review array bounds, method calls, and numeric conversions. Each runnable example is a complete Java 21 Main.java program with no input required.
What you will be able to do
- Classify a failure before choosing a debugging tool.
- Read a compiler diagnostic and repair the first relevant source problem.
- Follow a stack trace from the failure location through its callers.
- Use small reproducible inputs, labeled traces, and expected values to test a hypothesis.
- Inspect state with breakpoints and stepping in a local Java debugger.
- Fix bounds, arithmetic, and validation bugs and retain regression checks.
First decide what kind of failure you have
A student runs a learning-session report and says it is broken. That description is not yet a diagnosis. Did the compiler reject the source, did execution stop with an exception, or did the program finish with an incorrect result? Those three situations need different evidence.
A compiler diagnostic describes source that cannot be compiled as written. A runtime exception occurs after compilation while a particular execution path is running. A logic bug may produce ordinary output with no exception at all. A failure can also prevent a later bug from being reached, so repair and rerun in a controlled sequence.
The website runner distinguishes compilation failures, execution failures, and sandbox availability. If Docker is unavailable or a resource limit is reached, that is not automatically evidence of a Java syntax problem. Keep the exact source, input, expected outcome, actual outcome, and reported phase together.
Read the first useful compiler diagnostic in context
The missing-semicolon example intentionally does not compile. Read the file location, line number, message, and caret, then inspect that statement and the statement immediately before it. A parser may report the point where the grammar became impossible rather than the exact character you originally forgot.
Start with the first relevant error, make the smallest justified correction, and compile again. A missing brace or semicolon can produce several later messages; changing every highlighted line at once makes it harder to tell which messages were consequences of the first problem.
Compiler wording and exact line numbers can vary with the JDK and your edits. The meaningful observation here is a semicolon expected near the incomplete declaration, not a promise that every environment prints an identical paragraph. Save the original failing version so you can explain why the correction works.
Main.java · Diagnose a missing semicolon
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Diagnostic to look for: ';' expected. No successful result line is expected; line numbers may change when you edit.
Compilation must fail: the declaration of minutes needs a semicolon. The compiler may point near the following token; inspect the nearby preceding statement as well. No program output runs.
Check types, names, and scope rather than guessing from appearance
The second example looks like a numeric value to a human, but quotation marks make it a String. Java does not implicitly parse that text into an int. This compiler error is about incompatible types, not a runtime number-format failure, because execution has not started.
For a fixed example, use the numeric literal 25. For user-provided text, a deliberate parsing step may be appropriate and can fail at runtime for unsupported input. Changing the variable to String merely to remove the error would change the calculation's contract rather than repair the intended numeric data.
The same evidence-first approach applies to misspelled variables, inaccessible fields, and locals used outside their scope. Verify the declaration, visible type, spelling, and method signature. Do not make every field public or remove an interface just to silence a diagnostic whose real cause is a wrong call.
Main.java · A plausible value can still have the wrong type
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Diagnostic to look for: String cannot be converted to int. No successful result line is expected; line numbers may change when you edit.
Compilation must fail because quoted 25 is a String rather than an int. Use the numeric literal for fixed numeric data; parse external text deliberately when the actual input contract is textual.
Read a runtime stack trace from the failure location through callers
The array program compiles but asks a two-element array for minutes[2]. Legal indices are 0 and 1; length is the count, not the last index. The exception type identifies the category, and the first relevant application frame points to Main.last where the invalid access occurs.
The following application frames show how execution reached that method: report called last, and main called report. Read those frames as a chain of active calls rather than a list of independent failures. println never receives a completed report value because argument evaluation fails first.
In larger programs, library frames may appear above or below your own code. Inspect the relevant application frame and any Caused by chain before deciding what triggered the failure. A stack trace shows where failure became visible, not necessarily where the bad value was first created; trace that value back through the callers.
Main.java · Follow an array failure through three calls
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Diagnostic to look for: ArrayIndexOutOfBoundsException. No successful result line is expected; line numbers may change when you edit.
Compilation succeeds, but accessing index 2 in a two-element array fails. The trace identifies Main.last as the failing method, then Main.report and Main.main as callers. No println argument is completed, so there is no successful result line.
Read the failure in four steps
- Identify ArrayIndexOutOfBoundsException rather than treating the whole message as syntax feedback.
- Open the Main.last frame and inspect the array index expression.
- For a length of 2, predict valid indices 0 and 1; compare the actual requested index, 2.
- Follow report and main to see where the array came from, then define empty-input behavior before repairing last.
The highlighted line is evidence of the bad access. It is not proof that every caller and input contract is already correct.
A program can succeed operationally and still be wrong
The average example returns 2.0 without an exception, although five minutes across two sessions should average 2.5. Write that expected result down before editing. Without an independent expectation, it is easy to mistake a neat-looking printed number for a correct calculation.
Both operands in total / count are int, so integer division happens before assignment to double. The destination type cannot restore a fraction that has already been discarded. Inspect the intermediate expression and operand types, not only the final variable declaration.
Other quiet arithmetic defects include accumulating large totals in int or casting after an overflowing addition. A long destination alone does not widen an earlier int-plus-int expression. In the lab, a long accumulator widens each addition, and the average casts before division.
Main.java · Successful execution can still calculate the wrong average
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
2.0
The program runs successfully but the required arithmetic mean is 2.5. Both division operands are int, so fractional information is lost before the result is stored as double. A zero exit code is not proof of correctness.
Shrink the input and observe the state around one hypothesis
A two-element input is enough to expose an off-by-one loop. An input of {2, 3} is enough to expose fractional loss. Reducing a large report to a small reproducible case removes distractions while preserving the failure you are trying to understand.
Use a hypothesis you can disprove: perhaps the loop reaches index equal to length, or perhaps division uses two integer operands. Print a few labeled values immediately before the suspicious operation and compare them with your prediction. Change one relevant thing, rerun the same input, then test nearby cases.
Diagnostic expressions must not change the state you are inspecting. Printing index++ would advance the loop counter, and invoking a mutating method from a watch expression can alter the program. Avoid printing passwords, tokens, customer data, or entire production payloads; our traces contain only synthetic numeric teaching data.
Main.java · Observe a loop without changing its state
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
before index=0, length=2, total=0
after total=20
before index=1, length=2, total=20
after total=50
Final: 50
Two iterations visit indices 0 and 1. The labels show values before and after accumulation without incrementing anything in the diagnostic expression. Index 2 is never used to read the array.
Use local breakpoints to inspect the same evidence without extra prints
The website runner executes complete Main.java programs and shows output or diagnostics; it is not an interactive breakpoint debugger. For this optional exercise, copy the trace example into a local JDK project and start a Java debug session in an editor with Java debugging support, or use the JDK's jdb tool.
Place a breakpoint on the total += minutes[index] line and run in debug mode. Before that line executes, inspect index, minutes.length, minutes[index], and total. On the first stop they should be 0, 2, 20, and 0. Step over the addition and inspect total again; it should now be 20.
Step over executes a call without entering its implementation, step into can enter a called method, and step out resumes until the current method returns. Continue resumes until another breakpoint or stop. For an intermittent bound problem, a conditional breakpoint can target index == minutes.length before an invalid read; this is a local debugging technique, not a feature of the website Run code button.
Optional local debugger checklist
- Save the runnable trace example as Main.java in a local Java project.
- Set a breakpoint on the accumulation statement and launch in debug mode.
- Inspect index, length, current element, and total before the statement executes.
- Step over once, then compare total with its predicted value.
- Continue to the next iteration, or step into a helper method to inspect a call frame.
- Remove or disable temporary breakpoints after verifying the repair.
In the JDK command-line debugger, stop in Main.main establishes a method breakpoint, step can enter a call, and next steps within the current frame. This is a local JDK exercise, not a command to paste into Java source or the website Run code editor.
Repair the operation, not just the symptom
Casting total before division fixes the arithmetic operation. Casting total / count afterward only converts an already-truncated answer. Running both expressions side by side makes the distinction visible and gives a small test that can survive later refactoring.
Similarly, a loop that should visit every array element uses index < length, not index <= length. Changing an invalid read to always return zero, or stopping the loop one element too early, may remove the exception while silently dropping valid data. The expected total tells you whether the repair preserved the intended work.
For a method named last, minutes[length - 1] works only after defining how an empty or missing array is handled. Do not apply a mechanical minus-one change and declare every case fixed. A useful repair states the accepted inputs, output, and boundary behavior rather than merely making one failing run disappear.
Main.java · Fix the operation rather than the destination type
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
2.5
2.0
Casting before division produces floating-point division. Casting the already-truncated result afterward cannot restore the lost fraction. Compare the two expressions rather than assuming any double cast has the same effect.
Define invalid input separately from empty valid data
Our final report accepts an empty array as a valid report with total zero and average 0.0. It rejects null because no array was supplied, and it rejects negative minutes because this exercise models elapsed learning-session lengths. Those are explicit teaching policies, not automatic Java defaults.
Validate before using array length or an element in a calculation. A meaningful IllegalArgumentException tells the caller which contract was violated; replacing every exception with zero would make missing data indistinguishable from a genuinely empty report. Detailed try/catch and exception propagation belong to the next lesson.
The report methods only read the caller's array and return values. They do not sort, rewrite, save progress, or print. main owns the display and catches only the deliberately demonstrated validation failure. Keeping observation separate from calculation makes both tests and debugging easier.
Main.java · Separate an invalid argument from an empty valid report
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Empty total: 0
Empty average: 0.0
Rejected: Minutes must not be negative.
An empty array is valid under this explicit report policy, while negative minutes are rejected. The specific catch prints an expected validation failure; it does not turn unknown failures into a fake successful report.
Keep the evidence as a regression check
After the first repair, rerun the original failing input before trying new inputs. Then keep a small check for each discovered defect: an ordinary multi-element total, a fractional mean, an empty input, a one-element array, a large total, and rejected negative or null input.
The executable checks use if followed by throw new AssertionError rather than Java's assert keyword, which can be disabled unless assertions are enabled. They are a small standalone teaching harness, not a claim that a test framework or the website automatically checks every edited program.
Record the input, expected and actual result, failure category, observation, hypothesis, repair, and verification. Remove temporary noisy traces from the final model but keep the repeatable tests. The goal is an explained correction supported by evidence, not merely a green run after several unrelated edits.
Main.java · Preserve the cases that exposed the defects
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
3 regression checks passed.
Explicit checks fail loudly if the contract changes. These exact comparisons use deliberately exactly representable values; arbitrary floating-point calculations may need an appropriate tolerance. Explicit AssertionError checks run without the optional -ea flag.
Repair exercise: a mean lost its fraction
The average should be 2.5 for {2, 3}, but the program prints 2.0. Explain the operand types before editing, then repair the division and use a long accumulator. This focused exercise assumes a nonempty array of nonnegative minutes; the lab defines the missing boundary rules.
Main.java · Explain the truncated average
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
The starter compiles and currently prints:
2.0
Reveal the explained repair
The original division uses integer operands. A double cast on total before division keeps the fraction. A long accumulator also prevents totals of valid int values from overflowing an int accumulator. Neither change alone defines empty or null behavior, so those contracts are added explicitly in the lab.
Main.java · Preserve wide totals and fractional division
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
2.5
Independent lab: diagnose three defects in a session report
Diagnose three defects in a learning-session report: an inclusive array bound, a narrow total, and integer division. Record evidence before each repair. Finish with a read-only report model and repeatable checks; no course progress is stored or synchronized.
- Run the starter and record its phase, exception type, and first application frame. Explain why index equal to array length is invalid. Repair the loop so every element is visited once, including the last valid element.
- Implement static long totalMinutes(int[] minutes) on SessionReport. Reject null with IllegalArgumentException and Minutes must not be null. Reject any negative element with Minutes must not be negative. Use a long accumulator and do not mutate the array.
- Implement static double averageMinutes(int[] minutes) using the validated total. Define empty input as 0.0 and cast before division for nonempty input. A valid single session returns its own value as the average.
- Rerun {2, 3} after repairing bounds: a remaining 2.0 average exposes integer division. Test two Integer.MAX_VALUE elements to expose the narrow accumulator independently. Fix the causes rather than adding arbitrary constants to expected results.
- Use the supplied main to report {20, 30, 25}, empty input, a large total, and a caught negative-input error. Format the display average with Locale.ROOT while keeping the calculation method independent of printing.
- Keep explicit regression checks for empty, single, multi-element, fractional, large, null, and negative cases. Verify repeated queries leave input unchanged. Write an evidence note for each of the three defects and state which observation proved the correction.
Starter behavior: compilation succeeds, then an ArrayIndexOutOfBoundsException occurs in SessionReport.totalMinutes before the first report line is printed. Fix that access before using later output to investigate arithmetic.
The completed program should produce:
Sessions: 3
Total: 75
Average: 25.0
Empty total: 0
Empty average: 0.0
Large total: 4294967294
Rejected: Minutes must not be negative.
Main.java · Investigate the broken session report
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Stage 1 hints: isolate the runtime failure
Record the reported method and compare the final loop index with length. Replace the inclusive bound with a traversal that visits every valid element exactly once. Rerun the original case, then test one element and an empty array.
Stage 2 hints: inspect arithmetic separately
The ordinary report may now look right, but {2, 3} exposes fractional loss and two Integer.MAX_VALUE values expose overflow. Use a long return type and accumulator for the total, then cast before dividing for the mean. A cast after the operation cannot undo prior loss.
Stage 3 hints: define boundaries and retain evidence
Reject null and negative elements consistently, define the empty mean explicitly, and keep the model read-only. Copy arrays to verify no mutation. Use explicit checks that throw on mismatches, not disabled-by-default assertions or a broad catch that prints success.
Reveal the complete explained solution
Main.java · Complete the verified session report
Predict the result, edit Main.java, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
The off-by-one defect blocks the first run before the arithmetic bugs can be observed. The final enhanced-for loop visits each valid element without an index bound to miswrite. Long accumulation preserves large totals, casting before division preserves fractions, and explicit input policies prevent fake success on invalid data. Display formatting stays in main.
Test changes and boundaries
- Run the original starter to reproduce the bounds exception, then test the repaired total with {}, {0}, {7}, {20, 30, 25}, and {2, 3}. Verify both values and the number of processed elements.
- For {2, 3}, expect total 5 and average 2.5. Compare casting before division with casting after it and explain why only the first repairs the operation.
- For two Integer.MAX_VALUE values, expect total 4294967294 and average 2147483647.0. Confirm widening happens during accumulation rather than after an overflowing int total.
- Reject null and negative elements in the first, middle, and last position with exact messages. Empty input remains valid under this contract; a validation error must not be silently converted to an empty report.
- Copy arrays before repeated total/average calls and compare their contents afterward. Keep explicit failing checks for regressions, then remove temporary numeric traces from the final model.
Common mistakes and how to diagnose them
Editing several unrelated things after one failure
Save the reproducer and test one hypothesis at a time. Otherwise you cannot tell which change corrected the cause or which new change introduced another defect.
Reading only the bottom frame of a stack trace
Start with the exception and the relevant failing application frame, then follow callers. In the example main starts the chain, but last performs the invalid array access.
Treating every red message as a syntax error
Compilation, runtime exceptions, logic mistakes, and sandbox availability are distinct. Match your repair to the evidence and execution phase.
Catching everything and returning zero
That can hide a contract violation or programming bug as an apparently valid empty report. Handle only failures you can interpret meaningfully; detailed exception handling follows next.
Changing program state while observing it
Avoid increments, assignments, or mutating method calls in diagnostic expressions and debugger watches. Observations should not create a different execution path.
Declaring success after only the original input works
Retain the original case and add nearby boundaries, rejected inputs, and wide arithmetic. A green execution status alone cannot verify business results.
Frequently asked questions
Is every bug an exception?
No. Invalid source can fail compilation, execution can throw an exception, or the program can return a wrong result without any exception. Classify the observed failure before deciding how to inspect it.
Does the first stack frame always identify the original cause?
It identifies a recorded failure location, which may be inside a library or reflect a bad value created earlier. Inspect application callers and any cause chain to understand how the failure arose.
Can I use breakpoints in the website runner?
No. This runner provides compilation, execution, output, and diagnostics. The optional breakpoint exercise requires a local Java debug session; it does not imply an interactive debugger on this page.
Why not just print every variable?
Small labeled observations near a specific hypothesis are easier to interpret. Excessive output adds noise, may expose private data, and can reach runner output limits without identifying the cause.
Are Java assert statements always active?
No. They can be disabled unless assertions are enabled. This lesson's standalone checks use explicit if and throw new AssertionError so they run under the normal website runner configuration.
Should I catch Error to make the program keep running?
Not as a general repair. Error is also a specific Java type for serious conditions, distinct from the broad everyday word error. Do not swallow Throwable or serious failures to make a broken calculation appear successful.
Make each correction explainable and repeatable
Classify the failure, preserve a reproducer, inspect the first relevant evidence, and change one cause at a time. Stack traces explain active calls; labeled observations and breakpoints reveal values. Expected-result checks expose wrong answers even when execution succeeds.
Explain the difference between a missing semicolon, an invalid array index, and a truncated average. Then explain which regression case would catch each repair breaking again. Next: exceptions, try, catch, and finally, where you will handle failures deliberately without hiding unrelated defects.
Can you diagnose before you edit?
Answer six questions and read the explanations. Quiz answers are temporary and do not save course progress.
References and related learning
The session-report examples, explanations, and exercises are original SovranCode material. These official Java 21 references document stack traces, debugger operations, and numeric division.
- Java 21 Throwable: stack traces and causes
- Java 21 jdb: local breakpoints and stepping
- Java 21 specification: numeric division
Review arrays · Review numeric conversions · Java course roadmap.
Last updated: