Exception Handling in Java

Java Exceptions is a language tool to react to exceptional cases (errors) in the runtime.

In other words, if something went wrong you can throw or catch an exception.

Let’s take a look at the exception hierarchy in Java:

On the top is Throwable.

It’s a superclass for each exception and error in Java.

It contains the main 3 things:

  1. Message – specific details, e.g. FileNotFoundException contains the name of the file that can’t be found.
  2. Stacktrace – represents an array of stack elements with a name of the class and line number, so developers can easily understand who has thrown an exception.
  3. Cause – one throwable can be wrapped in another one, so this is a reference to a throwable that caused this throwable to get thrown.

The main subclasses are Exception, RuntimeException, and Error.

I’ll explain what are exception used for, best practices and frequently asked interview questions.

Java Exception Tutorial

There are 2 types of exceptions in Java: checked and unchecked.

Raising and handling both types of exceptions is more or less the same.

Let’s have a deeper look.

Checked Exceptions

Any subclass of Exception except RuntimeException is a checked exception.

That means you should declare it in method or constructor throws clause and you should handle it outside.

The most common checked exception list:

  • subclasses of IOException (FileNotFoundException, EOFException, ObjectStreamException)
  • exceptions related to concurrency (InterruptedException, IllegalThreadStateException)
  • subclasses of SQLException
  • text processing related exceptions like ParseException

For example, we have a class User:

package com.explainjava;
 
public class User {
    
    private String id;
    private String name;
 
    public User(String id, String name) {
        this.id = id;
        this.name = name;
    }
 
    public String getId() {
        return id;
    }
 
    public String getName() {
        return name;
    }
}

And we want to throw a checked exception if user not found by id.

Exception contains userId and builds a specific message:

package com.explainjava;
 
public class UserNotFoundException extends Exception {
 
    public UserNotFoundException(String userId) {
        super(String.format("User with id '%s' is not found", userId));
    }
}

And our service layer:

package com.explainjava;
 
import java.util.HashMap;
import java.util.Map;
 
public class UserService {
 
    private static final Map<String, User> ID_TO_USER = new HashMap<>();
 
    public User findOne(String id) throws UserNotFoundException {
        if (!ID_TO_USER.containsKey(id)) {
            throw new UserNotFoundException(id);
        }
        return ID_TO_USER.get(id);
    }
}

As you can see I had to declare UserNotFoundException in the method signature.

Unchecked Exceptions

Any subclass of RuntimeException or of Error is an unchecked exception.

Let’s take a deeper look at both of them.

Runtime Exception

If you take a look at Java exception hierarchy RuntimeException is a subclass of the Exception class.

It’s not a usual subclass.

RuntimeException and all its subclasses are unchecked exceptions.

That means you should not declare it in method or constructor throws clause.

The most common unchecked exception list:

  • The most popular is NullPointerException
  • IllegalArgumentException, IllegalStateException and its subclasses.
  • Collections and arrays related exceptions (ArrayIndexOutOfBoundsException, IndexOutOfBoundsException, ConcurrentModificationException)
  • Exceptions related to class casting like ClassCastException

Let’s refactor our UserNotFoundException and UserService to the unchecked exception.

We should make our self-made exception a subclass of RuntimeException.

package com.explainjava;
 
public class UserNotFoundException extends RuntimeException {
 
    public UserNotFoundException(String userId) {
        super(String.format("User with id '%s' is not found", userId));
    }
}

And remove throws UserNotFoundException from service:

package com.explainjava;
 
import java.util.HashMap;
import java.util.Map;
 
public class UserService {
 
    private Map<String, User> idToUser = new HashMap<>();
 
    public User findOne(String id) {
        if (!idToUser.containsKey(id)) {
            throw new UserNotFoundException(id);
        }
        return idToUser.get(id);
    }
}

Looks a little bit cleaner, but if you want to force Java exception handling outside than you should use checked exceptions.

Error

Java error is a critical application problem.

You should not try to catch and handle it.

It’s no sense.

You shouldn’t declare Java errors in method or constructor throws clause as well.

That’s why errors relate to unchecked Java exceptions.

Common errors list:

  • Errors that are subclasses of VirtualMachineError (OutOfMemoryError, StackOverflowError, InternalError)
  • Errors related to class definition and reflection, that are subclasses of LinkageError (IllegalAccessError, NoClassDefFoundError, NoSuchMethodError, UnsupportedClassVersionError)

If you see an error in logs that means you have to check carefully your code.

Sometimes you need to use special tools like profilers to find a problem.

How To Handle Exceptions

Let’s create a real-life situation.

A user can add a comment, but before storing new comment to the database we have to be sure that the user exists.

The comment looks like this:

package com.explainjava;
 
public class Comment {
 
    private User user;
    private String text;
 
    public Comment(User user, String text) {
        this.user = user;
        this.text = text;
    }
 
    public User getUser() {
        return user;
    }
 
    public String getText() {
        return text;
    }
}

There 2 options on how to handle exceptions:

  • Propagate exception to the next level
  • Catch the exception in catch block

Let’s take a deeper look at comment service examples.

How To Propagate Exception

Propagate an exception means you don’t want to handle such exception on the current layer and someone else should handle it for you on the upper level.

Example:

package com.explainjava;
 
import java.util.ArrayList;
import java.util.List;
 
public class CommentService {
 
    private static final List<Comment> COMMENTS = new ArrayList<>();
 
    private UserService userService;
 
    public CommentService(UserService userService) {
        this.userService = userService;
    }
 
    public void add(String userId, String text) throws UserNotFoundException {
        User user = userService.findOne(userId);
        COMMENTS.add(new Comment(user, text));
    }
 
}

As you can see I’ve added throws UserNotFoundException to add method signature.

In case of unchecked exceptions, you shouldn’t add it.

How To Catch Exception

Java exception handling mechanism provides a try-catch-finally tool.

Example:

try {
    // Your code that can throw an exception
} catch (YourException e) {
    // handle an exception here
} finally {
    // This block will be executed at the end even if new exception
    // will be thrown inside of catch block
}

You’re executing your code inside of try block.

If the exception occurs inside of try block you’ll catch it in the catch block.

At the end finally block will be executed. Usually, you can close resources there.

Example:

package com.explainjava;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
import java.util.ArrayList;
import java.util.List;
 
public class CommentService {
 
    private static final Logger LOGGER = LoggerFactory.getLogger(CommentService.class);
    private static final List<Comment> COMMENTS = new ArrayList<>();
 
    private UserService userService;
 
    public CommentService(UserService userService) {
        this.userService = userService;
    }
 
    public boolean add(String userId, String text) {
        try {
            User user = userService.findOne(userId);
            COMMENTS.add(new Comment(user, text));
            return true;
        } catch (UserNotFoundException e) {
            LOGGER.error("Error occurred during adding new comment: '" + e.getMessage() + "'.");
            return false;
        }
    }
}

As you can see if UserNotFoundException occurs inside of userService.findOne(userId) we can catch it and handle.

In our case I wrote a message to log and return false, that means the comment is not added.

Java Exception Handling Best Practises

I’ll write 5 the most important things that you should or should not do when you’re working with Java exceptions.

Throw Specific Exceptions

Java allows you to say: “Hey, my method throws some exception!”.

Sounds very generic.

The user doesn’t understand what exception is it and how he should handle it.

public void hello() throws Exception

It’s a bad practice.

It’s better to let a user know what specific Java exceptions method can throw.

For example, your method needs to read a file and parse a text to say “hello”.

public void hello() throws IOException, ParseException

So you have 2 checked exceptions to declare.

Catch Specific Exceptions

More or less the same as a first rule.

You should catch specific exceptions instead of a generic exception.

For example, it’s better to write like this:

try {
    if (condition1 ) {
        throw new FirstException();
    }
    if (condition2) {
       throw new SecondException();
    }
} catch (FirstException e) {
    // handle first exception
} catch (SecondException e) {
    // handle second exception
}

Instead of handling all Java exceptions in single catch block:

try {
    if (condition1 ) {
        throw new FirstException();
    }
    if (condition2) {
       throw new SecondException();
    }
} catch (Exception e) {
    // handle all exceptions here
}

Don’t Throw Or Catch Throwable

It’s a bad idea to make your own exception subclass of Throwable.

It’s bad practice to throw new Throwable() or catch (Throwable e) as well.

As far as you know Error is a subclass of Throwable.

And I mentioned above that you should not handle Java errors.

So if you will try to catch Throwable it’s possible that you’ll catch an Error that shouldn’t be handled.

Don’t Ignore Exceptions

You shouldn’t keep catch block empty.

You should log a message that exceptions occurred at least.

Something could happen and you will never know about it.

Write Informative Exception Messages

Add as more information there as you can.

It’s really easier to understand what exactly went wrong.

Don’t care if a message will be large.

This data can help you to fix bugs faster.

Interview Questions

I’ve prepared the most popular interview questions about Java exceptions.

You should definitely know the answers before an interview.

What Is Java Exception Hierarchy?

You should know that Throwable is the main class.

Exception and Error extend Throwable.

RuntimeException extends Exception.

Take a look at the exception hierarchy image at the beginning of the article.

You should know a few specific examples of Exception, RuntimeException and Error as well.

What Types Of Exceptions Do You Know? When To Use Checked and Unchecked Exceptions?

There are 2 types of exceptions in Java: checked and unchecked.

You should use checked exception when you want to force the user to handle an exceptional situation.

It should be used for expected exceptions.

Unchecked exceptions are used for everything else.

But in my opinion, checked exceptions make code ugly a little bit.

Some popular Java frameworks (Spring or Hibernate) or even other languages don’t use checked exceptions at all.

So I would say it depends on project rules and developer preferences.

Is Error Checked or Unchecked Exception?

You shouldn’t add error to method or constructor throws cause, that’s why an error is an unchecked exception.

Does Finally Block Executed If You Will Throw an Exception In Catch Block?

Let’s imagine you caught an exception and program raised a new exception during exception handling.

Will finally block be executed?

Example:

try {
     System.out.println("TRY");
     throw new IllegalArgumentException();
} catch (IllegalArgumentException e) {
     System.out.println("CATCH");
     throw new IllegalArgumentException();
} finally {
     System.out.println("FINALLY");
}

The answer is yes, finally block will be executed anyway.

Output:

TRY
CATCH
FINALLY

Can You Rethrow an Exception?

Yes, Throwable has a cause field, so you can specify an exception that was a reason for a current one.

Example:

try {
    throw new NullPointerException();
} catch (IllegalArgumentException e) {
    throw new IllegalArgumentException("Problem occurred during execution", e);
}

We wrapped 1st exception (NPE) into IllegalArgumentException and specified an exception message.

Is Throwable Interface Or Class

Throwable sound like an interface, it’s true.

But in fact, it’s a class.

James Gosling said:

The reason that the Throwable and the rest of those guys are not interfaces is because we decided, or I decided fairly early on.
I decided that I wanted to have some state associated with every exception that gets thrown.
And you can’t do that with interfaces; you can only do that with classes.
The state that’s there is basically standard. There’s a message, there’s a snapshot, stuff like that — that’s always there. and also, if you make Throwable an interface the temptation is to assign, to make any old object be a Throwable thing.
It feels stylistically that throwing general objects is probably a bad idea, that the things you want to throw really ought to be things that are intended to be exceptions that really capture the nature of the exception and what went on.
They’re not just general data structures.

You can check exceptions related questions in my Java interview questions collection post, some updates should appear from time to time.

That’s it, ask your questions in comments.

Leave a Comment