Nested Block in Java
You can stack one try
block inside another. This is helpful for handling different types of mistakes in different ways; for example, inner try
blocks handle less serious problems, while outer try
blocks may catch more significant ones.
If an exception is created inside an inner try
block and its corresponding catch
handler fails to capture it, it is passed to the outer try
block. In order to find a match, the outer try
block then checks its own catch
handlers. This procedure keeps going up the chain of nested try
blocks until either all of the nested try
blocks have been used up or a catch
statement fits the exception type. The exception will be handled by the default exception handler of the Java runtime system if no catch
matches.
Here’s an example of nested try
blocks:
public class NestedTryDemo {
public static void main(String[] args) {
try { // Outer try block
int[] outerArray = {1, 2, 3};
System.out.println("Outer block: Accessing outerArray: " + outerArray);
try { // Inner try block
int[] innerArray = new int[44];
System.out.println("Inner block: Trying to divide by zero...");
int divisionResult = 10 / 0; // Generates ArithmeticException
System.out.println("Inner block: Division result: " + divisionResult); // This won't be displayed
} catch (ArrayIndexOutOfBoundsException innerExc) {
// This catch only handles ArrayIndexOutOfBoundsException
System.out.println("Inner Catch: Caught ArrayIndexOutOfBoundsException: " + innerExc.getMessage());
}
System.out.println("Outer block: After inner try-catch."); // This won't be displayed if inner throws ArithmeticException
} catch (ArithmeticException outerExc) {
// This catch handles ArithmeticException propagated from inner try
System.out.println("Outer Catch: Caught ArithmeticException: " + outerExc.getMessage());
} catch (Exception genericExc) {
// Generic catch for any other unexpected exceptions
System.out.println("Outer Catch: Caught a generic exception: " + genericExc.getMessage());
} finally {
System.out.println("Finally block: Always executed.");
}
}
}
Output:
Outer block: Accessing outerArray: 1
Inner block: Trying to divide by zero...
Outer Catch: Caught ArithmeticException: / by zero
Finally block: Always executed.
The inner catch
(for ArrayIndexOutOfBoundsException
) in this example does not capture the ArithmeticException
thrown in the inner try
section. The catch
(ArithmeticException outerExc
) block successfully captures it once it propagates to the outer try
block.
Built-in Exceptions
All exceptions in Java are represented by classes derived from the Throwable
class, which sits at the top of the exception hierarchy. Throwable
has two direct subclasses: Error
and Exception
.
- Error: These consist of major issues like
OutOfMemoryError
orStackOverflowError
that arise in the Java Virtual Machine (JVM) itself. Programmers normally have no control over errors, and applications typically don’t address them. - Exception: Exceptions are errors that arise from program activity and are typically handled by your program. A significant subclass of the
Exception
class is calledRuntimeException
.
Moreover, Java divides exceptions into two primary groups:
- Unchecked Exceptions: Subclasses of
RuntimeException
are known as Unchecked Exceptions. If a method handles or declares these exceptions in athrows
list, the compiler doesn’t verify it. Usually, these are unexpected occurrences that are frequently caused by program defects.- Examples include:
ArithmeticException
(e.g., division by zero),NullPointerException
(invalid use of a null reference),ArrayIndexOutOfBoundsException
(array index out of bounds),InputMismatchException
, andNumberFormatException
.
- Examples include:
- Checked Exceptions: These are subclasses of
Exception
but notRuntimeException
. An application should be able to handle these exceptions, which stand for expected events. Thethrows
keyword must be used to declare a method that can throw a checked exception but does not handle it; otherwise, a compile-time error will occur.- Examples include:
IOException
(I/O error),FileNotFoundException
,SQLException
, andInterruptedException
.
- Examples include:
Let’s look at an example demonstrating ArithmeticException
and ArrayIndexOutOfBoundsException
:
public class BuiltInExceptionsDemo {
public static void main(String[] args) {
// Demonstrate ArithmeticException
try {
int result = 10 / 0; // This will cause an ArithmeticException
System.out.println("Result: " + result); // This line won't execute
} catch (ArithmeticException e) {
System.out.println("Caught ArithmeticException: " + e.getMessage());
}
System.out.println("\n--- Next Example ---\n");
// Demonstrate ArrayIndexOutOfBoundsException
try {
int[] numbers = new int[35];
System.out.println(numbers[36]); // This will cause an ArrayIndexOutOfBoundsException
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Caught ArrayIndexOutOfBoundsException: " + e.getMessage());
}
System.out.println("Program continues after handling exceptions.");
}
}
Output:
Caught ArithmeticException: / by zero
--- Next Example ---
Caught ArrayIndexOutOfBoundsException: Index 10 out of bounds for length 5
Program continues after handling exceptions.
The catch
block runs in these examples if a matched exception is thrown, while the try
block watches the code for mistakes.
Exception Propagation
If an exception is not handled at the location where it occurred, it propagates up the call stack. This process is known as exception propagation.
The default exception handler in the Java Virtual Machine will catch any exceptions that your program fails to capture. In addition to terminating program execution, this handler outputs a stack trace from the exception’s location and shows a text explaining the issue. The stack trace, which displays the order of method calls that resulted in the issue, is essential for debugging.
Programmers can use the throws
keyword to explicitly propagate exceptions. A method’s throws
clause in its method signature must declare any checked exceptions that it can generate but does not internally handle with a try-catch
block. The responsibility is passed up the call stack to calling methods, who are then notified that they must either handle the declared exception or declare it themselves. It is not necessary to declare unchecked exceptions (subclasses of RuntimeException
) in a throws
clause since Java assumes that a function may throw them implicitly.
Consider this propagation example using throws
:
import java.io.IOException;
public class ExceptionPropagationDemo {
// Method that declares it throws IOException
static void readFile() throws IOException {
System.out.println("Inside readFile() method.");
// Simulating an I/O error by directly throwing an IOException
throw new IOException("Failed to read file data.");
}
// Method that calls readFile() and propagates the IOException
static void processFile() throws IOException {
System.out.println("Inside processFile() method.");
readFile(); // This call must either be in a try-catch or declared to throw IOException
}
public static void main(String[] args) {
System.out.println("Inside main() method.");
try {
processFile(); // This call must be in a try-catch because processFile() declares IOException
} catch (IOException e) {
System.out.println("Caught IOException in main(): " + e.getMessage());
}
System.out.println("Program finished.");
}
}
Output:
Inside main() method.
Inside processFile() method.
Inside readFile() method.
Caught IOException in main(): Failed to read file data.
Program finished.
This illustrates how the exception is propagated up the call stack until it is handled: readFile()
throws an IOException
, which processFile()
does not handle, so it declares throws IOException
. Before main()
calls processFile()
, it must surround it in a try-catch
block to handle the IOException
.
Custom Exceptions
Although Java has built-in exceptions for many common errors, you may need to handle situations unique to your application, in which case you can create custom exception classes to manage errors as part of your program’s overall exception handling strategy.
Creating a custom exception is as easy as defining a subclass of Exception
(for a checked exception) or RuntimeException
(for an unchecked exception). Like any other class, your custom exception class can have fields and methods to convey specific error information, you should provide constructors that allow a detailed message to be passed, and you may want to override the toString()
method to provide a customised description of the exception when it’s printed.
Here’s an example of a custom checked exception:
// Custom checked exception for an invalid age
class InvalidAgeException extends Exception {
private int age;
public InvalidAgeException(String message, int age) {
super(message); // Call the superclass (Exception) constructor
this.age = age;
}
public int getAge() {
return age;
}
// Override toString to provide a custom message
@Override
public String toString() {
return "InvalidAgeException: Age provided was " + age + ". " + super.getMessage();
}
}
public class CustomExceptionDemo {
public static void validateAge(int age) throws InvalidAgeException {
if (age < 0 || age > 150) {
throw new InvalidAgeException("Age must be between 0 and 150.", age);
} else if (age < 18) {
throw new InvalidAgeException("User must be at least 18 years old.", age);
}
System.out.println("Age " + age + " is valid.");
}
public static void main(String[] args) {
System.out.println("Testing age validation:");
try {
validateAge(25);
validateAge(15);
validateAge(-5); // This will cause an exception
} catch (InvalidAgeException e) {
System.out.println("Caught custom exception: " + e.toString());
System.out.println("Problematic age: " + e.getAge());
} finally {
System.out.println("Custom exception handling demonstration complete.");
}
}
}
Output:
Testing age validation:
Age 25 is valid.
Caught custom exception: InvalidAgeException: Age provided was 15. User must be at least 18 years old.
Problematic age: 15
Custom exception handling demonstration complete.
InvalidAgeException
is a custom checked exception in this example, and the validateAge
method declares that it throws InvalidAgeException
. If an invalid age is passed, an InvalidAgeException
object is created and thrown, which is then caught and processed in main()
. The overridden toString()
method gives a more detailed output about the particular age that caused the problem.