Introduction
In Java programming, many things can go wrong; the server might be down, a file may be missing, or a method may not work as expected. When something like this happens, it is an exceptional condition. An exception occurs when there is a disruption in the execution of a program.
Java provides us with a way to deal with exceptional conditions when they occur so they don't terminate our program. It is called exception handling.
What is an Exception?
An exception in Java is an event or error that disrupts the normal flow of a program. It usually occurs during the execution of a program. In Java, exceptions are represented as objects, derived from the class Exception. When an exception occurs, an exception object is generated and thrown by the program.
Syntax and structure of an exception in Java
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class ExceptionExample {
public static void main(String[] args) {
try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException err) {
System.err.println("Error reading or closing the file: " + e.getMessage());
}
}
}
In this example:
We import the necessary classes. These classes are for reading files and handling input/output operations.
The main method: The code is executed inside the method which is the entry point of the program.
Try-with-resources Block: Inside the parentheses after the try keyword in the try block, we create a BuferedReader instance, wrapped around a fileReader, to read the contents of a file named "file.txt"
Inside the try block, we declare a string variable named line. The readLine() method of the BufferedReader is called in a loop to read each line of the file. The loop continues until readLine() returns null, indicating the end of the file.
In the loop, each line that is read from the file is printed to the console.
If an exception occurs during file reading, the code in the catch block is executed. The caught exception object is assigned to the variable err and an error message is printed to the console.
The error message printed in the catch block provides information about the specific error that occurred.
Types of Exceptions
There are two main types of exceptions in Java:
checked exceptions
unchecked exceptions(also known as runtime exceptions)
Checked exceptions
These are exceptions that are checked when the program is being compiled. The compiler makes sure that they are either caught or handled by the method by using the ‘throws’ clause. Some examples of checked exceptions include:
SQLException: this exception is thrown when there is an issue with database access or query execution.
IOException: thrown when there is an I/O error, such as file not found.
Unchecked Exceptions(runtime exceptions)
These are exceptions that occur at runtime due to programming errors or unforeseen conditions. Unchecked exceptions are typically used for errors that should be fixed. Some examples are:
ArithmeticException :
This exception is thrown when an arithmetic operation fails.
NullPointerexception :
This is thrown when an attempt is made to access or call a method on a null object.
All exceptions in Java are subclasses of the base class “Throwable”. The ‘throwable’ class provides common methods and properties for exceptions.
We can also create custom exceptions in Java by extending the ‘Exception’ class or its subclasses to handle specific errors.
Handling Exceptions - Try-Catch and Finally
Exception handling in Java is a mechanism to handle exceptional situations or errors that may arise during the execution of a program.
We can also say it is a way of planning what to do in case a code generates an error during run-time, this is based on knowing that a particular code block is risky, so you write code to handle what happens when an exception arises.
Try-Catch and Finally
To handle exceptions, Java provides us with the try-catch block. The code that might cause an exception is placed within the try block, the potential exceptions that are caught are handled in the catch block.
try block
try {
Foo f = a.doSomething();
int g = f.getSomething();
//this code is risky as it might fail, so we place it in the try block.
}
In the code snippet above, the code that may cause an exception is wrapped in the try block. The purpose of the try block is to hold code that may cause an exception. It is like 'trying' a risky code.
catch block
The catch block contains code that will run if the code in the try block throws an exception.
try {
Foo f = a.doSometing();
Int g = f.getSomething();
// the code here runs first, but it throws an exception.
} catch (Exception ex){
System.out.println(“code failed to run”);
// this code will run because the code in the try block has thrown an exception.
}
System.outprinln(“this is the rest of the code …”);
// After the catch block runs, the method continues.
finally
A finally block is a block that contains code that must run regardless of an exception. It is commonly used for clean-up tasks such as closing database connections and closing files.
try {
openFile();
b.readFile();
} catch (Exception ex) {
ex.printStackTrace();
} finally {
closeFile();
// this code must run regardless of whether the code in try or catch block runs.
}
A final block is optional. It lets us put all cleanup code in one place so we don’t have to duplicate it everywhere like this:
try {
openFile();
b.readFile();
// the clean up code
closeFile();
} catch (Exception ex) {
ex.printStackTrace();
// the clean up code duplicated;
closeFile();
}
Putting them altogether
try {
openFile();
b.readFile();
} catch (Exception ex) {
ex.printStackTrace();
} finally {
closeFile();
}
If the code in the try block fails, the program moves to the catch block. After the catch block executes, the finally block will run. When the finally block completes, the rest of the method continues.
If there is no exception (code in the try block works), the flow control skips the catch block and moves to the finally block. When the finally block completes, the rest of the method continues.
The finally block runs no matter what, even if the try or catch block has a return statement.
Throwing Exceptions
Using the throw statement to raise exceptions
The throw statement is used to explicitly raise exceptions. It can be used to throw built-in exceptions and custom exceptions that the programmer creates.
public class ExceptionExample {
public static void main(String[] args) {
try {
divide(10, 0);
} catch (ArithmeticException e) {
System.err.println("Error: " + e.getMessage());
}
}
public static void divide(int numerator, int denominator) {
if (denominator == 0) {
throw new ArithmeticException("Division by zero");
}
int result = numerator / denominator;
System.out.println("Result: " + result);
}
}
In the example above, the divide
method is called with parameters 10
and 0
. If the denominator
is 0
, the throw
statement is executed, throwing an ArithmeticException
with the error message "Division by zero". The exception is then caught in the catch
block, where the error message is printed to the console.
The throw
statement allows you to explicitly raise exceptions when certain conditions or error scenarios are encountered in your code, enabling you to handle them appropriately.
Creating Custom Exception Classes
You can create custom exception classes by extending the “Exception” class or one of its subclasses.
public class MyOwnException extends Exception {
public MyOwnException() {
super();
}
public MyOwnException(String message) {
super(message);
}
public MyOwnException(String message, Throwable cause) {
super(message, cause);
}
}
In the code above, myOwnException is a custom exception class that extends the Exception class. It provides multiple constructors. The constructors use the super() call to make the constructors of the parent Exception class. A custom class can have additional fields and methods depending on the specific needs of the programmer.
To use your custom exception class, you can throw an instance of the calls using the throw statement as shown here:
public class CustomExceptionExample {
public static void main(String[] args) {
try {
processInput(-1);
} catch (CustomException e) {
System.err.println("Custom Exception caught: " + e.getMessage());
}
public static void processInput(int value) throws CustomException {
if (value < 0) {
throw new MyOwnException("Invalid input value");
}
// Process the input
}
}
Here, we throw an instance of MyOwnException with a custom error message. The exception is then caught in the catch block and the error message is printed to the console.
By creating custom exception classes, you can provide more specific and meaningful error handling in your code, making it easy to identify and handle exceptional situations.
Exception Propagation and Handling
This section explains how errors or exceptional situations are passed through the program and how they are dealt with. Java provides a mechanism for handling errors that may occur during program execution.
Exception Propagation
When an error occurs in a program, an exception object is created to represent that error. This object contains information about the error including its type and any other relevant data. The exception is then thrown from the point it occurred. Propagation means the exception is raised and passed up the call-stack searching for an appropriate exception handler to handle it. If no handler is found, the program terminates with an error message.
Exception Handling
Exception handling involves catching and processing exceptions to prevent the program from terminating. Exception handling can help one can deal with errors and take action before terminating the program.
Exception propagation and handling are crucial for writing reliable programs. Through proper exception handling, one can make sure that a program will handle exceptional situations without crashing.
Conclusion
In conclusion, exception handling is a vital aspect of Java programming that enables the handling of exceptional conditions and errors that may arise during program execution. Exceptions are represented as objects and can be passed up the call stack until they are caught and addressed by suitable exception handlers. By utilizing try-catch-finally blocks, you can gracefully manage exceptions, deliver error messages, and perform necessary cleanup operations. Java also supports the creation of custom exception classes, allowing for the handling of specific types of exceptions. Effective exception handling is essential for developing reliable and robust programs, ensuring that exceptional situations are appropriately managed to prevent program crashes or unexpected behavior.