Exceptions

Exception vs. Error

Exceptions and errors are deviant behaviour from the correct one. Exceptions can be checked (expected) or unchecked (not expected) and errors are usually non-recoverable JVM errors (not expected).

graph TB; A[java.lang.Throwable] A --> B(java.lang.Exception) A --> C(java.lang.Error) B --> D(java.lang.RuntimeException)

java.lang.Throwable parent class of all exceptions in Java.

Checked Exceptions: All subclasses of java.lang.Exceptions except java.lang.RuntimeException

Unchecked Exceptions: All subclasses of java.lang.RuntimeException

We can handle all of them with catch blocks but good practice not to handle or declare unchecked exceptions and errors.

Checked Exceptions

Java follows the principle of handle or declare when it comes to checked exceptions. So we either use catch blocks to handle or declare after containing method’s signature.

throw

Since exceptions are classes, always use new to throw.

throw new SQLException();   // valid
throw SQLException();       // invalid

We can pass an optional message into exception’s constructor.

throw new SQlException("lorem ipsum dolor sit amet");

throw can lead to unreachable code causing compiler-error.

try{
    throw new SQLException();
    System.out.println("Hello!")    // unreachable code, compiler error
}

throws

Method exception declaration: Declare all expected (Checked only) exceptions in the declaration and no need to handle them inside that method. The parent method must also either declare or handle (aka Propagation).

void foo() throws IOException{		// declare in calling method too
	bar();
}

void bar() throws IOException{ }

------------------------------------------------------------------

void foo(){         // no propagation req since we handled it inside method body
	try{
		bar();
	}
	catch(IOException e){ }			// catch in calling method (handle)
}

void bar() throws IOException{ }

Unchecked Exceptions:

// we don't need to handle-or-declare Unchecked Exceptions or Errors

void fun(){         // no declaration or handling req
    throw new RuntimeException();
}
// we can declare with "throws" even if method body doesn't throw the exception but we will need to handle or declare in calling method

void foo() throws IOException{ }

void bar() throws IOException{       // declare (propagate)
// try{         
    foo();
// }
// catch(IOException e){ }      // or handle
}

Overriding

An overriding method (subclass) can declare fewer exceptions than those already declared by the overriden method (superclass). In simple words, we can’t add more checked exceptions to subclass method, but can add unchecked ones and subclass of exception declared in superclass method.

class A{
	void foo() throws IOException{ }
}

class B extends A{
	void foo(){ }		// valid
}

------------------------------------------------------------------

class A{
	void foo() throws IOException{ }
}

class B extends A {
	voif foo() throws IOException, SQLException{ }		// invalid
}

Since the exceptions list are inherited by subclass, we can’t add more because the overriden method can be called from parent reference and it won’t expect the new error introduced in subclass method and catch can be written without it.

Below security issue will ensue if we allow a subclass method to add exceptions to list during compile-time:

class A{
	void foo(){ }
}

class B extends A {
	void foo() throws IOException{ }       // compiler-error; but lets suppose this is valid

    public static void main(String args[]){
        super.foo();		// no handling required; class A declared no exceptions so need to catch or declare any; this is a security lapse

        B obj = new B();
        obj.foo();		   // have to handle or declare IOException due to this line
    }
}

Printing an exception

catch (Exception e){
	System.out.println(e);
	System.out.println(e.getMessage());
	System.out.println(e.printStackTrace());
}

Handling (try-catch-finally)

Each try must have atleast a single catch OR a finally block.

try{	}
catch(IOException	e){	}   // exception must be getting thrown in the try block
finally{	}

------------------------

try{    }
catch(IOException e){ }   // exception must be getting thrown in the try block

------------------------

try{    }
finally{    }

If a particular exception is never thrown in try block, we can’t write catch for it:

try{ }                       // empty try block
catch(IOException e){       // compiler-error; Exception 'java.io.IOException' is never thrown in the corresponding try block
    System.out.println(e);
}

-----------------------------------------

try{ }
catch(Exception e){       // valid!
    System.out.println(e);
}

One try can have multiple catch blocks (beware of unreachable ones).

catch block chaining: Super class is allowed only after more specific ones because there may be exceptions to be caught that may not belong to subclass catch block but will be caught by superclass catch block.

catch(IOException e){ }		// subclass
catch(Exception e){ }		// superclass; valid

catch(Exception e){ }		// superclass
catch(IOException e){ }		// unreachable code; compiler-error

Multi-catch block: Separated by | character, only a single object e of either. It can’t have related classes since we’re going to enter the catch block anyway no matter the exception types we specify if they are all related, so what’s even the point.

catch(Exception1 | Exception2 | Exception3 e){	}
catch(FileNotFoundException | IOException e){	}	// invalid; FileNotFoundException is subclass of IOException

finally

Executes once, always. Even when there is a return inside try or catch.

class A{
    static int foo(){
      try{
          return 1;
      }
      catch(Exception e){
          return 2;
      }
      finally{
          return 3;
      }
    }
}

public class MyClass {
    public static void main(String args[]) {
      System.out.println(A.foo());				// prints 3
    }
}

NOTE: There is only one case when finally doesn’t execute, that is when we use System.exit().

try-with-resources

try(var f = new File("my.txt")){	}

We can open resources in try and they get destroyed after try gets over even if there is a catch block. This destruction takes place as soon as try gets over and any finally will execute after that. Also, the resources are closed in the reverse order in which they were created.

try(var f = new File("my.txt"); var x = new File("foo.txt");){	}	// last semicolon is optional
// implicit finally; resources closed in reverse order of creation

catch(Exception e){	}	// optional catch

finally{ }      // optional finally too (both catch and finally need not be there unlike normal try)
try(var f = new File("my.txt"), var x = new File("foo.txt")){	}	// compiler-error; no comma for separator allowed

Closeable and AutoCloseable

Only classes that implement Closeable and AutoCloseable can be used with try-with-resources. Only difference is that Closeable’s close() method declares IOException and AutoCloseable’s close() declares Exception.

Final Resources

Resources can be created outside try and used inside but they need to be final or effectively final. They are closed in the reverse order of declaration in try block though.

final var f = new File("foo.txt");
var b = new File("bar.txt");

try(f; var l = new File("lorem.txt"); b){	}

/* closing order: 
	bar.txt
	lorem.txt
	foo.txt
*/

The above code fails if the resource b is not effectively final throughout the scope.

final var f = new File("foo.txt");
var b = new File("bar.txt");

try(f; var l = new File("lorem.txt"); b){	}	// compiler-error

b = null;		// b modified; so not effectively final

Suppressed Exceptions with try-with-resources

If two exceptions are thrown, one in try and the other in close() method following that, the first one is treated as a primary one while the second one is suppressed and put into a Throwble[] array accessible using e.getSuppressed().

class All implements AutoCloseable{
    public void close(){
        throw new NullPointerException("bar");
    }
}
public class MyClass {
    public static void main(String args[]) {
        try(var f = new All()){
            throw new ArithmeticException("foo");
        }
        catch(Exception e){
            System.out.println(e);
            for(Throwable t : e.getSuppressed())
                System.out.println(t.getMessage());
        }
    }
}

/*
java.lang.ArithmeticException: foo
bar
*/

Primary Exception - ArithmeticException (“foo”)

Suppressed Exception(s) - NullPointerException (“bar”) on close() method call after try-with-resources

Lost Exceptions with finally

Any exception thrown in finally block will supersede every other prior exceptions (they are lost, not even suppressed). This is a bad Java behaviour and exists only for backward compatibility.

class All implements AutoCloseable{
    public void close(){
        throw new NullPointerException("bar");
    }
}
public class MyClass {
    public static void main(String args[]) {
        try(var f = new All()){
            throw new ArithmeticException("foo");
        }
        finally{throw new NullPointerException("lorem");}
    }
}

/*
Exception in thread "main" java.lang.NullPointerException: lorem
	at MyClass.main(MyClass.java:11)
*/