An exception is an error that occurs while your program is running.
A classic example is to divide by 0:
int a = 65 / 0;
If you run this code, your program will crash:
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Junk.main(Junk.java:5)
Another example is trying to access an array outside of its bounds.
int[] arr = new int[20];
arr[25] = 56;
This code will also cause a crash:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 25
at Junk.main(Junk.java:6)
There are many different types of exceptions, here are a few examples:
Exception Type | Description |
---|---|
ArithmeticException |
An error while performing arithmetic. Most like dividing by 0. |
ArrayIndexOutOfBoundsException |
Trying to access an element of an array beyond its size. |
FileNotFoundException |
Trying to open a file that does not exist. |
NullPointerException |
Trying to access an object using a reference that points to null. |
These are just some specific examples of common exceptions. There are many more exceptions than this. You can even create your own exceptions.
For many exceptions, the compiler doesn’t do anything to let you know that they might occur when the program runs. These are called unchecked exceptions (because the compiler doesn’t check for them.) The ArithmeticException
, ArrayIndexOutOfBoundsException
, and NullPointerException
above are unchecked exceptions. If one of these exceptions occurs while your program is running and you haven’t done anything to handle it, then your program just crashes.
An unchecked exception is an exception that the compiler doesn’t check for or require you to handle. If you don’t handle the exception, then your program can compile and run, but it will crash if the exception occurs.
For other exceptions, the compiler does check to see if they might possibly occur and forces you to write your code in such a way to handle the exception if it does. These are called checked exceptions. If your program uses code that might generate a checked exception, then you are required to handle that exception if it occurs. FileNotFoundException
is an example of a checked exception. If you call a method that might cause a FileNotFoundException
, your program will not even compile until you have exception handling code in place.
A checked exception is one that the compiler checks for and requires you to handle. If you don’t handle the exception, then your program won’t compile.
So how do we handle exceptions and not crash? We write extra code that only runs if the exception occurs.
Consider the following simple code to read in an integer from the user:
import java.util.Scanner;
public class ScannerDemo {
public static void main(String[] args) {
Scanner userInput = new Scanner(System.in);
// Input a line of input from the user
System.out.print("Please enter an integer: ");
String inputLine = userInput.nextLine();
// Convert that line of input to an integer
int inputInt = Integer.parseInt(inputLine);
System.out.print("Thanks for the integer: ");
System.out.println(inputInt);
}
}
When the user inputs a number, the output is exactly like we would predict:
Please enter an integer: 42
Thanks for the integer: 42
However, if the user enters something other than an integer:
Please enter an integer: Bob
Exception in thread "main" java.lang.NumberFormatException: For input string: "Bob"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Integer.parseInt(Integer.java:580)
at java.lang.Integer.parseInt(Integer.java:615)
at ScannerDemo.main(ScannerDemo.java:10)
This invalid input triggered an exception: parseInt
was expecting a string containing numbers, but a non-numeric string was entered instead. It wasn’t sure how to handle the error, so it raised an exception. Our program didn’t handle that exception, so Java triggered a crash.
It is not ok for invalid input to cause a crash. Your program should never crash, no matter what the user does. If your program crashes, you have a bug.
We can fix this bug by adding a try/catch block for exception handling.
Consider the following version of our input program, now with exception handling:
import java.util.Scanner;
public class ScannerDemo {
public static void main(String[] args) {
Scanner userInput = new Scanner(System.in);
// Input a line of input from the user
System.out.print("Please enter an integer: ");
String inputLine = userInput.nextLine();
int inputInt = -1;
try {
// Convert that line of input to an integer
inputInt = Integer.parseInt(inputLine);
} catch (NumberFormatException e) {
System.out.println("Invalid input!");
}
System.out.print("Thanks for the integer: ");
System.out.println(inputInt);
}
}
Let’s walk through the changes:
We’ve added a try block to the code. Inside the try block, we put the lines of code that might cause an exception. In this case, we know that parseInt
might cause an exception, so we put that line inside the try block.
After that, we start our catch block. We specifically name the exception that we think might happen. (In this case, we know the exception we are worried about is the NumberFormatException
because we saw it listed in the error message when our program crashed.)
Inside the catch block, we put code that should run in case the named exception occurs.
The code inside of the catch block only runs if the exception occurs. If the exception doesn’t occur, then the code inside the catch block does not run.
When we run this program with the invalid input, we get the following:
Please enter an integer: Bob
Invalid input!
Thanks for the integer: -1
Instead of a crash we print an error message and give the integer a default value of -1. This is not the best way to handle that exception. Instead, we should probably just keep asking the user for a number until they give us one.
Let’s add a while loop with a boolean flag so that we keep asking the user for input until we get a valid integer:
import java.util.Scanner;
public class ScannerDemo {
public static void main(String[] args) {
Scanner userInput = new Scanner(System.in);
int inputInt = -1;
// Setup a flag that we can use to signal when we do or don't have a valid int
boolean needToInputInteger = true;
while (needToInputInteger) {
// Set our flag to false so we don't loop again unless there is an error.
needToInputInteger = false;
// Input a line of input from the user
System.out.print("Please enter an integer: ");
String inputLine = userInput.nextLine();
try {
// Convert that line of input to an integer
inputInt = Integer.parseInt(inputLine);
} catch (NumberFormatException e) {
// There was an error, print our message and set our flag so we try again.
System.out.println("Invalid input!");
needToInputInteger = true;
}
}
System.out.print("Thanks for the integer: ");
System.out.println(inputInt);
}
}
Someone running the program with invalid inputs (and finally a valid one) might get the following:
Please enter an integer: Bob
Invalid input!
Please enter an integer: Fred
Invalid input!
Please enter an integer: 45
Thanks for the integer: 45
So far we have discussed what exceptions are and how you can catch exceptions thrown by existing code. What if you want your program to throw an exception?
There are many situations where throwing your own exception is useful. Let’s consider one such situation: An error occurring inside the constructor.
Consider the following class for a circle:
public class Circle {
public double r;
public Circle(double r) {
this.r = r;
}
public double area() {
return Math.PI * this.r * this.r;
}
}
That Circle class is fairly simple, right? It is hard to imagine there being a situation where the constructor can encounter an error, but there is: What if someone tried to create a circle with a negative radius. A negative radius doesn’t make sense, and we shouldn’t allow it. But how do we, from inside the Circle constructor, communicate that error back up to whoever tried to create such an invalid circle? We use an exception.
Consider the following class for a circle that checks to make sure the r
argument to the constructor is valid:
public class Circle {
public double r;
public Circle(double r) {
if (r >= 0) {
this.r = r;
} else {
throw new IllegalArgumentException("r must be greater than or equal to 0");
}
}
public double area() {
return Math.PI * this.r * this.r;
}
}
There are multiple things about this code that need explanation:
What is the throw
keyword? When you want to raise (or throw) an exception, you use the Java keyword throw
.
What is an IllegalArgumentException
? Exceptions are just objects, like almost everything else in Java. In this case, we are making use of an exception that is defined for us in the Java standard library. It is meant to be used when an argument to a method is not valid due to its value. (Such as in this example.)
Why are we using new
with the IllegalArgumentException
? Because exceptions are objects, before you can throw one you need to actually create an instance of one. So, we are instantiating an IllegalArgumentException
and passing the constructor a message to be stored inside the exception.
What is with the message? We added a message to our exception ("r must be greater than or equal to 0"). If our exception gets thrown in a real program and a programmer needs to debug and figure out why then this message will help them determine why the exception was thrown.