Exception Handling Fundamentals
A Java exception is an object that describes an exceptional condition that has occurred in a code. When an exceptional condition arises, an object representing that exception is created and thrown in the method that causes the error. That method may choose to handle that exception itself or pass it on.
An exception can be generated by
Java run-time system - an error that violets the rule of Java language
or they can be manually generated by code to report error conditions to the caller of a method.
Java exceptions is managed by five keywords:
try - a statement that you want to monitor for exceptions are contained within a try block
catch - exceptions you want to handle are implemented in the catch block
throw - if you want to manually throw exception you need to use the keyword throw
throws - if you want to pass the exception to the calling method then use the throws keyword
finally - any code that must be executed after try blocks complete are defined in finally block
try{
//block of code to monitor for errors
}
catch(ExceptionType1 ex){
//exception handler for ExceptionType1
}
catch(ExceptionType2 ex){
//exception handler for ExceptionType2
}
finally{
//block of code to be executed after try block ends
}
Exception Types
All exception types are subclasses of the built-in class Throwable.
Exception class is used for exceptional conditions which the user program should catch and also the class that you will subclass to create custom exception types.
Checked Exception -
These exceptions represent expected and recoverable conditions that can occur during the execution of a program.
Checked exceptions are exceptions that the compiler requires you to handle explicitly using try-catch blocks or declare them in the method signature using the throws keyword. The compiler enforces the handling of checked exceptions to ensure that developers acknowledge and handle potential errors.
Checked exceptions are subclasses of java.lang.Exception, excluding RuntimeException and its subclasses.
Examples - IOException, SQLException, and ClassNotFoundException.
Unchecked Exception -
These exceptions represent programming errors or unexpected conditions that could have been avoided by proper coding practices.
The compiler does not enforce the handling of unchecked exceptions. However, it is generally good practice to handle them appropriately to ensure robust and reliable code.
They are subclasses of java.lang.RuntimeException.
Example - NullPointerException, ArrayIndexOutOfBoundsException, and IllegalArgumentException.
Error class defines exceptions that are not expected to be caught under normal circumstances, it is used by Java run-time system to indicate errors having to do with the run-time environments.
- Example - StackOverflow
Uncaught exceptions
In the below program, there is an invalid operation performed to divide a number by zero.
public class DivideByZero {
public static void main(String[] args) {
int d = 0;
int a = 42/d;
}
}
When we run the above code, the Java run-time system detects the attempt to divide by zero, it constructs a new exception object and then throws this exception.
This causes the execution to stop because once an exception has been thrown it must be caught by an exception handler and dealt with immediately. In this example, we haven't given any exception handler of our own so the exception is caught by the default handler provided by the Java run-time system.
Any exception that is not caught by your program will ultimately be processed by the default handler. The default handler displays the string describing the exception, prints a stack trace from the point at which the exception occurred and terminates the program.
Output
Exception in thread "main" java.lang.ArithmeticException: / by zero at advanced.exceptionalhandling.DivideByZero.main(DivideByZero.java:6)
Here ArithmeticException is a subclass of RuntimeException that's why the compiler has not been forced to handle this exception.
Using try and catch
As we have seen above when the exception occurred the program was terminated with an exception message displayed by Java run time, this lead to a bad user experience so it is important to handle such exceptions.
Exception handling allows you to fix errors.
It prevents the program from automatically terminating.
To handle an exception in Java, you can use the try-catch block. The try block contains the code that you want to execute, and the catch block contains the code that you want to execute if an exception occurs.
try {
// This code may throw an exception.
} catch (Exception e) {
// This code will execute if an exception occurs.
}
Example - After adding the try and catch block, the program is not terminated.
The goal of most well-constructed catch clauses should be to resolve the exceptional condition and then continue on as if the error had never happened.
throw statement
To throw an exception explicitly we can use a throw statement.
throw ThrowableInstance;
Here, ThrowableInstance must be an object Throwable or a subclass of Throwable.
The flow of execution stops immediately after the throw statement; any subsequent statements are not executed.
The nearest enclosing try block is inspected to see if it has a catch statement that matches the type of exception.
If it does find a match, control is transferred to that statement. If not, then the next enclosing try statement is inspected, and so on. If no matching catch is found, then the default exception handler halts the program and prints the stack trace.
public class ThrowDemo {
static void demoproc(){
try{
throw new NullPointerException("demo"); // it will create an instance of NullPointerException class and throw the exception
}catch (NullPointerException ex){ // it will catch the NullPointerException and throw it again
System.out.println("Caught in catch statement: "+ex);
throw ex;
}
}
public static void main(String[] args) {
try{
demoproc();
}catch (NullPointerException ex){ // it will catch exception thrown by catch block in demoproc() method
System.out.println("Recaught in main: "+ex);
}
}
}
throws statement
If you don't want to handle an exception in a method where an exception can be thrown then you can bypass the handling of an exception to the calling method using the throws statement in the method declaration.
A throws clause lists the types of exceptions that a method might throw. This is necessary for all exceptions, except those of type Error or RuntimeException, or any of their subclasses.
All other exceptions that a method can throw must be declared in the throws clause.
type method-name(parameter-list) throws exception-list
{
// body of method
}
Below code will have compile time error as it has not handled the exception thrown in the program.
public class ThrowsDemo {
static void throwOne(){
System.out.println("Inside throw one");
throw new IllegalAccessException("demo");
}
public static void main(String[] args) {
throwOne();
}
}
To handle this exception we first need to use the throws statement in throwOne() method and handle this exception in the main method using the try and catch block.
public class ThrowsDemo {
static void throwOne() throws IllegalAccessException{
System.out.println("Inside throw one");
throw new IllegalAccessException("demo");
}
public static void main(String[] args) {
try {
throwOne();
}catch (IllegalAccessException ex){
System.out.println("Exception is handled in main method.");
}
}
}
finally statement
finally creates a block of code that will be executed after a try /catch block has been completed and before the code following the try/catch block.
The finally statement in Java is used in exception handling and ensures that a block of code is always executed, regardless of whether an exception occurs or not.
Any time a method is about to return to the caller from inside a try/catch block, via an uncaught exception or an explicit return statement, the finally clause is also executed just before the method returns.
The primary purposes of using the finally statement are:
Resource cleanup: You can use the finally block to release resources like database connections, file handles, network connections, or any other resources that need to be properly closed or released, regardless of whether an exception occurred or not. This helps prevent resource leaks and ensures proper cleanup.
Guaranteed execution: The finally block provides a way to guarantee that certain critical statements are executed, even if an exception is thrown and caught. This is useful in situations where you want to ensure that specific code is executed regardless of any exceptional conditions.
public class FinallyDemo {
static void procA(){
try{
System.out.println("Inside procA");
throw new RuntimeException("demo");
}finally {
System.out.println("procA's finally"); // before throwing exception this statement will be executed
}
}
static void procB(){
try{
System.out.println("Inside procB");
return;
}finally {
System.out.println("procB's finally"); // before returning from exception this statement will be executed
}
}
static void procC(){
try{
System.out.println("Inside procC");
}finally {
System.out.println("procC's finally");
}
}
public static void main(String[] args) {
try{
procA();
}catch (Exception e){
System.out.println("Exception caught in main method");
}
procB();
procC();
}
}
Creating Custom Exceptions
To define your own exception type in Java we need to subclass Exception class.
MyException is a custom/user-defined exception; it has an overloaded method toString() which is defined in the Throwable class
public class MyException extends Exception{
private int detail;
public MyException(int a){
this.detail = a;
}
public String toString(){
return "My Exception [" + detail + "]";
}
}
The below code is a demo for throwing MyException and handling the same in main() method.
public class MyExceptionDemo {
static void compute(int a) throws MyException{
System.out.println("Called compute (" + a + ")");
if(a>10) throw new MyException(a);
System.out.println("Normal exit");
}
public static void main(String[] args) {
try{
compute(1);
compute(20);
} catch (MyException e){
e.printStackTrace();
System.out.println("Caught " + e);
}
}
}