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:
- Message – specific details, e.g.
FileNotFoundException
contains the name of the file that can’t be found. - 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.
- 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.