Exercise 2.2: Error Handling and Debugging
1 Purpose
The purpose of this exercise is to introduce the fundamental concepts of error handling and debugging in Java. No program is perfect, and a crucial skill for any developer is to be able to identify, understand, and fix errors. You will learn the difference between compilation and runtime errors, understand what exceptions are, and use try...catch blocks to handle them gracefully.
2 What You’ll Accomplish
By the end of this exercise, you will have successfully:
- Identified and corrected common compilation errors.
- Intentionally created and analyzed runtime errors to understand their cause.
- Explained the concept of an
Exceptionin Java. - Used a
try...catchblock to prevent a runtime exception from crashing your program. - Handled common built-in exceptions like
NullPointerExceptionandArrayIndexOutOfBoundsException.
This exercise maps to the following program and course learning outcomes:
- Program Learning Outcomes (PLOs):
- 3. Apply concepts: You will apply core programming concepts related to error handling and program flow.
- 5. Implement solutions: You will implement code that correctly anticipates and manages errors.
- Course Learning Outcomes (CLOs):
- 1. Develop algorithmic solution: You will modify your code to handle alternative (error) pathways.
- 6. Debug and test programs: This exercise is entirely focused on debugging and handling errors.
This exercise aligns with the O*NET SOC Code 15-1251.00 for Computer Programmers.
| Learning Objective | O*NET KSAs | Technologies Used |
|---|---|---|
| Differentiate between compilation and runtime errors. | Knowledge: Computers and Electronics Skills: Troubleshooting |
Development environment software: VS Code, JDK |
Implement try...catch blocks to handle exceptions. |
Knowledge: Programming Skills: Programming, Critical Thinking |
Object or component oriented development software: Java |
Debug common exceptions like NullPointerException. |
Skills: Troubleshooting, Debugging | Development environment software: Java, VS Code Terminal |
3 Understanding Errors
In programming, an error, often called a “bug,” is a mistake in the code that causes it to behave in an unexpected way or to stop running altogether. Learning to “debug” (find and fix bugs) is a core part of being a programmer. There are two main categories of errors you will encounter in a compiled language like Java.
Compilation Errors
A compilation error (or syntax error) is a mistake that violates the rules of the Java language. The Java compiler (javac) will find these errors when you try to build your project. Your code will not compile, and no .class file will be created, until you fix them.
Common examples include: - Forgetting a semicolon (;) at the end of a line. - A typo in a variable name or keyword (e.g., Sytem.out.println instead of System.out.println). - Mismatched brackets ({ or (). - Trying to assign a value of the wrong type to a variable (e.g., int x = "hello";).
You don’t always have to wait for compilation to find basic errors. Modern IDEs like Visual Studio Code have built-in support for linters.
A linter is a tool that analyzes your source code as you type to flag potential programming errors, stylistic errors, and suspicious constructs. In VS Code, these are often highlighted with red squiggly underlines, and a summary of all detected issues is available in the “Problems” pane (usually found in the bottom panel, or accessible via View > Problems) and also with the Ctrl+Shift+M keyboard shortcut.
This provides immediate feedback, helping you fix syntax errors and other common mistakes long before you even try to compile your code.
Runtime Errors
A runtime error occurs while your program is running. The code is syntactically correct and compiled successfully, but something unexpected happens during execution that the program doesn’t know how to handle. In Java, most runtime errors result in an Exception.
When an exception occurs, the Java Virtual Machine (JVM) creates an Exception object that contains information about the error, including where it happened. If this exception is not “caught” (handled), the program will terminate and print a “stack trace” to the console, which helps you find the source of the error.
4 Your Task: Let’s Break Things
The best way to learn about errors is to cause them intentionally. In this exercise, you will use the App.java file from the previous exercises to create and fix several common errors.
Scenario 1: The Null Pointer
A NullPointerException is one of the most common runtime errors in Java. It happens when you try to use a variable that holds the value null as if it were pointing to a real object with methods.
Modify
App.java: Open yourApp.javafile. Inside themainmethod, add the following lines. We’ll declare aStringvariable but assign it the valuenull.String text = null; System.out.println("The length of the text is: " + text.length());Compile and Run: Open the integrated terminal and compile your code with
mvn clean compile. It should compile successfully because there are no syntax errors. Now, run the application withmvn exec:java.Analyze the Crash: Your program will crash and you will see a
NullPointerExceptionwith a stack trace. Notice how the stack trace tells you the exact line number where the error occurred because you tried to call the.length()method on anullreference.Fix the Error: To fix this, you must ensure the variable is not
nullbefore you use it. For now, just remove the two lines you added. In a real application, you would add a check likeif (text != null).
Remember the linter and the “Problems” pane we discussed earlier? If you look at the line text.length() in VS Code, you should see a yellow squiggly underline.
If you hover over it or check the Problems pane, you will see a warning like: “Null pointer access: The variable text can only be null at this location”. This is the linter proactively warning you that you are trying to use a variable that might be null, which could lead to a runtime error. This is a powerful feature that helps you catch potential bugs before you even run your program!
Scenario 2: Out of Bounds
An ArrayIndexOutOfBoundsException occurs when you try to access an array element using an index that doesn’t exist. For example, if an array has 3 elements (at indices 0, 1, and 2), trying to access index 3 will cause this error.
Modify
App.java: Inside themainmethod, add the following lines. Theargsarray holds command-line arguments. Unless you provide some when you run the program, its length is 0.System.out.println("First command-line argument: " + args[0]);Compile and Run: Compile with
mvn clean compileand run withmvn exec:java.Analyze the Crash: The program will crash with an
ArrayIndexOutOfBoundsException.Fix the Error: Remove the line you added. A proper fix would be to check the length of the array first:
if (args.length > 0).
You might wonder why the linter caught the potential NullPointerException but not this ArrayIndexOutOfBoundsException.
The linter performs static analysis, meaning it analyzes your code without actually running it. It could see that the text variable was assigned null and never changed, so it could predict the error.
However, the contents of the args array are determined at runtime (when the user runs the program), not at compile time. The linter has no way of knowing what a user will provide as command-line arguments, so it cannot warn you that args[0] might not exist.
This is why you should almost always perform a bounds check before accessing an array element, especially if the array’s size can change. Try it!
// Good Practice: Check the array length before access
if (args.length > 0) {
System.out.println("First argument: " + args[0]);
} else {
System.out.println("No command-line arguments were provided.");
}5 Handling Exceptions with try...catch
Crashing is not very user-friendly. Instead of letting the program terminate, we can “catch” the exception and handle it gracefully. We do this using a try...catch block.
tryblock: You place the code that might cause an exception inside thetryblock.catchblock: If an exception occurs in thetryblock, the JVM immediately jumps to thecatchblock. The code in thecatchblock is then executed.
Scenario 3: Graceful Handling
Let’s repeat Scenario 1, but this time, we’ll catch the exception.
Modify
App.java: Wrap the code that caused theNullPointerExceptionin atry...catchblock.String text = null; try { // This line might cause a NullPointerException System.out.println("The length of the text is: " + text.length()); } catch (NullPointerException e) { // This block runs ONLY if a NullPointerException occurs System.out.println("Error: Cannot measure the length of a non-existent string."); // The 'e' variable holds the exception object. We can print its stack trace for debugging. // e.printStackTrace(); } System.out.println("Program continues after the error.");Compile and Run: Compile and run your code again.
Analyze the Output: Notice that the program no longer crashes! It prints your custom error message and then continues to the final
printlnstatement. This is the power of exception handling—it allows you to control the flow of your program even when errors occur.
6 Reflect and Review
In your Microsoft Teams Student Notebook, answer the following:
- 3 differences between a compilation error and a runtime error.
- 2 reasons why you would use a
try...catchblock. - 1 question you still have about exceptions.
Answer the following questions in your Microsoft Teams Student Notebook.
- If your Java code has a syntax error, what happens when you run
mvn clean compile? - What is a “stack trace” and why is it useful?
- What is the purpose of the
catchblock in atry...catchstatement? - Besides
NullPointerExceptionandArrayIndexOutOfBoundsException, find and name one other common built-in Java exception.