15 Java - Exception Handling

15 Java - Exception Handling

Photo by Zetong Li on Unsplash

What is Exception?

  • It's an event, that occurs during the execution of the program.

  • It will disrupt your program normal flow.

  • It creates the Exception Object, which contain information about the Error like

    • Type of Exception and Message

    • Stack Trace etc.

  • Runtime system use this Exception Object and find the class which can handle it.

package learn.Exception;

public class Main {
    public static void main(String[] args) {
        Main sampleObj = new Main();
        sampleObj.method1();
    }

    private void method1(){
        method2();
    }

    private void method2() {
        method3();
    }

    private void method3() {
        int b = 5/0;
    }
}
// Output -------
Exception in thread "main" java.lang.ArithmeticException: / by zero
    at learn.Exception.Main.method3(Main.java:18)
    at learn.Exception.Main.method2(Main.java:14)
    at learn.Exception.Main.method1(Main.java:10)
    at learn.Exception.Main.main(Main.java:6)

Exception Hierarchy

Difference between Error and Exception

An Error is also a kind of runtime exception, but the main difference is that an Error cannot be handled by the code, while Exceptions can be handled.

Unchecked / Runtime Exception

These are the exceptions which occurs during runtime and compiler not forcing us to handle them.

public class Main {
    public static void main(String[] args) {
        Main sampleObj = new Main();
        method1();
    }
    private static void method1() {
        throw new ArithmeticException();
    }
}

ClassCastException

public class Main {
    public static void main(String[] args) {
        Object val = 0;
        System.out.println((String) val);
    }
}
Exception in thread "main" java.lang.ClassCastException: 
class java.lang.Integer cannot be cast to class java.lang.String 
(java.lang.Integer and java.lang.String are in module java.base 
of loader 'bootstrap')
at learn.Exception.Main.main(Main.java:6)

ArithmeticException

public class Main {
    public static void main(String[] args) {
        int val = 5/0;
    }
}
Exception in thread "main" java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String (java.lang.Integer and java.lang.String are in module java.base of loader 'bootstrap')
    at learn.Exception.Main.main(Main.java:6)

IndexOutOfBoundException

  • ArrayIndexOutOfBoundsException
public class Main {
    public static void main(String[] args) {
        int[] val = new int[2];
        System.out.println(val[3]);
    }
}
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 2
    at learn.Exception.Main.main(Main.java:6)
  • StringIndexOutOfBoundsException
public class Main {
    public static void main(String[] args) {
        String val = "hello";
        System.out.println(val.charAt(5));
    }
}
Exception in thread "main" java.lang.StringIndexOutOfBoundsException: String index out of range: 5
    at java.base/java.lang.StringLatin1.charAt(StringLatin1.java:48)
    at java.base/java.lang.String.charAt(String.java:1519)
    at learn.Exception.Main.main(Main.java:6)

NullPointerException

public class Main {
    public static void main(String[] args) {
        String val = null;
        System.out.println(val.charAt(0));
    }
}
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.charAt(int)" because "val" is null
    at learn.Exception.Main.main(Main.java:6)

IllegalArgumentException

If you are sending wrong data then this error is thrown.

public class Main {
    public static void main(String[] args) {
        int val = Integer.parseInt("acb");
    }
}
Exception in thread "main" java.lang.NumberFormatException: For input string: "acb"
    at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67)
    at java.base/java.lang.Integer.parseInt(Integer.java:668)
    at java.base/java.lang.Integer.parseInt(Integer.java:786)
    at learn.Exception.Main.main(Main.java:5)

Checked / Compile time Exception

Compiler verifies them during the compile time of the code and if not handled properly, code compilation will fail.

Example:

java: unreported exception java.lang.ClassNotFoundException; must be caught or declared to be thrown

Handle the Exception

Using throws

throws tells that, this method MIGHT throw this exception (or might not), so please caller you handle it appropriately.

Caller class need to then take Care

public class Main {
    public static void main(String[] args) throws ClassNotFoundException {
        method1();
    }

    private static void method1() throws ClassNotFoundException {
        throw new ClassNotFoundException();
    }
}

Using try/catch block

public class Main {
    public static void main(String[] args) {
        method1();
    }

    private static void method1() {
        try{
            throw new ClassNotFoundException();
        }
        catch (ClassNotFoundException exceptionObject){
            //handle this exception scenario like logging
            exceptionObject.printStackTrace();
        }
    }
}

Or

public class Main {
    public static void main(String[] args) {
        try {
            method1();
        } catch (ClassNotFoundException exceptionObject) {
            //handle it
        }
    }

    private static void method1() throws ClassNotFoundException {
        throw new ClassNotFoundException();
    }
}

Using: try, catch, finally, throw, throws

Try/Catch

  • Try block specify the code which can throw exception

  • Try block is followed either by Catch block or finally block

  • Catch block is used to catch all the exception which can be thrown in the try block.

  • Multiple catch block can be used.

Catch block, can only catch exception which can be thrown by try block.

Catch all Exception Object

public class Main {
    public static void main(String[] args) {
        try {
            method1("dummy");
        }
        catch (ClassNotFoundException exceptionObject) {
            //handle it
        }
        catch (Exception exceptionObject){
            //handle it
        }
    }

    private static void method1(String name) throws ClassNotFoundException, InterruptedException {
        if (name.equals("dummy")) {
            throw new ClassNotFoundException();
        }
        else if (name.equals("interrupted")){
            throw new InterruptedException();
        }
    }
}

Incorrect order

Already Caught by above catch

Catch Multiple Exceptions in One Catch Block

public class Main {
    public static void main(String[] args) {
        try {
            method1("dummy");
        }
        catch (ClassNotFoundException | InterruptedException exceptionObject) {
            //handle it
        }
        catch (Exception exceptionObject){
            //handle it
        }
    }

    private static void method1(String name) throws ClassNotFoundException, InterruptedException {
        if (name.equals("dummy")) {
            throw new ClassNotFoundException();
        }
        else if (name.equals("interrupted")){
            throw new InterruptedException();
        }
    }
}

try/catch/finally Or try/finally block

  • Finally block can be use after try or after catch block.

  • Finally block will always get executed, either if you just return from try block or from catch block.

  • At most, we can add only 1 finally block.

  • Mostly used for closing the object, adding logs etc.

  • If JVM related issues like out of memory, system shut down or our process is forcefully killed. Then finally block do not get exectued.

Example 1:

public class Main {
    public static void main(String[] args) {
        try {
            method1("dummy");
        }
        catch (ClassNotFoundException  exceptionObject) {
            //handle it
        }
        finally {
            // do something here
        }
    }

    private static void method1(String name) throws ClassNotFoundException {
        if (name.equals("dummy")) {
            throw new ClassNotFoundException();
        }
    }
}

Example 2: No catch block. (throws in signature)

public class Main {
    public static void main(String[] args) throws ClassNotFoundException {
        try {
            method1("dummy");
        }
        finally {
            // do something here
        }
    }

    private static void method1(String name) throws ClassNotFoundException {
        if (name.equals("dummy")) {
            throw new ClassNotFoundException();
        }
    }
}

Example 3: No catch & no throws for run time exception.

public class Main {
    public static void main(String[] args) {
        try {
            method1("dummy");
        }
        finally {
            // do something here
        }
    }

    private static void method1(String name) throws ArithmeticException {
        if (name.equals("dummy")) {
            throw new ArithmeticException();
        }
    }
}

Example 4: return in try block

public class Main {
    public static void main(String[] args) {
        try {
            method1("dummy");
            return;
        }
        finally {
            System.out.println("Inside finally");
        }
    }

    private static void method1(String name)  {
    }
}

/* Output ---
Inside finally
*/

throw

  • It is used to throw a new exception or

  • To re-throw the exception

public class Main {
    public static void main(String[] args) throws ClassNotFoundException {
        try {
            method1();
        }
        catch (ClassNotFoundException e){
            throw e;
        }
    }

    private static void method1() throws ClassNotFoundException {
        throw new ClassNotFoundException();
    }
}

Creating custom / User-Defined Exception class

public class MyCustomException extends Exception{
    MyCustomException(String message){
        super(message);
    }
}

Usage

public class Main {
    public static void main(String[] args) {
        try {
            method1();
        }
        catch (MyCustomException e){
            // handle it
        }
    }

    private static void method1() throws MyCustomException {
        throw new MyCustomException("some issue arise");
    }
}

Why to handle the Exception?

  • It makes our code clean by separating the error handling code from regular code.

  • It allows program to recover from the error.

  • It allow us to add more information, which support debugging.

  • Improves security, by hiding the sensitive information.

Without Exception handling, we have to do this,

Notice 2 things: Readability and erro code need to be returned.

public int myMethod(int schoolClassNumber){
    int errorCode = 0; // 0 means no success

    if (schoolClassNumber > 0 && schoolClassNumber <= 12){
        int noOfStudents = getStudentCapacityOfClass(schoolClassNumber);
        if (noOfStudents != 0){
            String[] names = new String[noOfStudents];
            if (names!=null && names.length>0){
                names[0] = "new Value";
            }
            else{
                return -3;
            }
        }
        else{
            return -2;
        }
    }
    else{
        errorCode = -1;
    }

    return errorCode;
}

With Exception Handling

public int myMethod(int schoolClassNumber){
    try{
        int noOfStudents = getStudentCapacityOfClass(schoolClassNumber);
        String[] names = new String[noOfStudents];
        names[0] = "new Value";
    }
    catch (IndexOutOfBoundsException exceptionObject){
        // do something
    }
    catch (Exception exceptionObject){
        // do something
    }
    return -1;
}

Expensive

  • Remember, Exception hanlding is little expensive, if stack trace is huge and it is not handled or handled at parent class.

  • If the throws exception propagates from caller to caller this is stored in stack memory. so this also occupies some space. (Like Recurrsion)

  • Try to avoid using exception handling, if you can. Or Handle the exception in the class where it was originated. In this away it will be super fast.

Example:

public int myMethod(int a, int b){
    int val;

    try{
        val = a/b;
    }
    catch (ArithmeticException exp){
        val = -1;
    }
    return val;
}

Simplified: (Faster Approach)

public int myMethod(int a, int b){
    if (b==0) return -1;
    int val = a/b;

    return val;
}