Java Exceptions
Some errors in your code do not prevent the program from running and, in this case, the program will only crash while trying to execute a "broken" line: a line with an error called an exception. Thus, exceptions are the errors detected during the program execution (at runtime), whereas syntax errors are those detected during program compilation into byte-code. An exception interrupts the normal flow of a program.
Let's consider some types of exceptions, the conditions where exceptions occur, and how to avoid them.
ArithmeticException
Suppose you are writing a program that reads two integers from the standard input and then outputs the result of the integer division of the first number by the second one. Take a look at the code below:
import java.util.Scanner;
public class ArithmeticExceptionDemo {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int a = scanner.nextInt();
int b = scanner.nextInt();
System.out.println(a / b); // an exception is possible here!
System.out.println("finished");
}
}
If the user passes 9
and 3
as the input values, the program outputs 3
as well as the "finished"
string. But if the second number is 0
, the program throws an exception because of division by zero is undefined, and the "finished"
string is not printed at all.
Exception in thread "main" java.lang.ArithmeticException: / by zero
at ArithmeticExceptionDemo.main(ArithmeticExceptionDemo.java:11)
All the code before the exception is executed properly, and everything after is not.
The displayed message shows the cause of the exception — ("/ by zero"
), the file, and the line number where it has occurred (ArithmeticExceptionDemo.java:11o
). The provided information is useful for developers, but it is not very meaningful for the end-users of the program.
To avoid the exception, we can check the value before the division, and, if the value is zero, print a message. Here is another version of the program: If the second number is zero, the program will print the "Division by zero!" string.
import java.util.Scanner;
public class ArithmeticExceptionDemo {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int a = scanner.nextInt();
int b = scanner.nextInt();
if (b == 0) {
System.out.println("Division by zero!");
} else {
System.out.println(a / b);
}
System.out.println("finished");
}
}
Look at an input example where we try to input zero as a divider:
3 0
The result is:
Division by zero!
finished
As you can see, the new version of the program does not throw an exception and always successfully finishes. Additionally, it prints a user-friendly message instead of the standard message.
NumberFormatException
Another situation to consider is when you are trying to convert a string into an integer number: If the string has an unsuitable format, the code will throw an exception.
The following program reads a line from the standard input and then outputs the number that follows it.
import java.util.Scanner;
public class NumberFormatExceptionDemo {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String input = scanner.nextLine();
int number = Integer.parseInt(input); // an exception is possible here!
System.out.println(number + 1);
}
}
It works pretty well if the input line is a correct integer number, but if the input is not correct (e.g. "121a"
), the program will fail:
Exception in thread "main" java.lang.NumberFormatException: For input string: "121a"
at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.base/java.lang.Integer.parseInt(Integer.java:652)
at java.base/java.lang.Integer.parseInt(Integer.java:770)
at NumberFormatExceptionDemo.main(NumberFormatExceptionDemo.java:9)
This message displays the type of exception (NumberFormatException
) and the passed input string. The place where the exception occurred is shown in the last line of the message:
- the filename is
NumberFormatExceptionDemo.java
; - the
main
method; - at line 9.
All the previous lines of the message show the positions inside the parseInt
method which is part of the standard library.
To avoid this exception, it is possible to check the input string by using a regular expression. In case the input is not correct, we can output a warning message:
import java.util.Scanner;
public class NumberFormatExceptionDemo {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String input = scanner.nextLine();
if (input.matches("\\d+")) { // it checks if the input line contains only digits
int number = Integer.parseInt(input);
System.out.println(number + 1);
} else {
System.out.println("Incorrect number: " + input);
}
}
}
If the input line is "121a", the program will not stop, and it will print the message:
Incorrect number: 121a
Hierarchy of exceptions
The following picture illustrates the simplified hierarchy of exceptions:
The base class for all exceptions is java.lang.Throwable
. This class provides a set of common methods for all exceptions:
String getMessage()
returns the detailed string message of this exception object;Throwable getCause()
returns the cause of this exception ornull
if the cause is nonexistent or unknown;printStackTrace()
prints the stack trace on the standard error stream.
We will return to the methods and constructors of this class in the following topics.
The Throwable
class has two direct subclasses: java.lang.Error
and java.lang.Exception
.
- subclasses of the
Error
class represent low-level exceptions in the JVM, for example:OutOfMemoryError
,StackOverflowError
; - subclasses of the
Exception
class deal with exceptional events inside applications, such as:RuntimeException
,IOException
; - the
RuntimeException
class is a rather special subclass ofException
. It represents so-called unchecked exceptions, including:ArithmeticException
,NumberFormatException
,NullPointerException
.
While developing an application, you will normally process objects of the Exception
class and its subclasses. We won't discuss Error
and its subclasses here.
The four basic exception classes (Throwable
, Exception
, RuntimeException
and Error
) are located in the java.lang
package. They do not need to be imported. Yet their subclasses might be placed in different packages.
Checked and unchecked exceptions
All exceptions can be divided into two groups: checked and unchecked. They are functionally equivalent but there is a difference from the compiler's point of view.
1. Checked exceptions are represented by the Exception
class, excluding the RuntimeException
subclass. The compiler checks whether the programmer expects the occurrence of such exceptions in a program or not.
If a method throws a checked exception, this must be marked in the declaration using the special throws
keyword. Otherwise, the program will not compile.
Let's take a look at the example. We use the Scanner
class, which you are already familiar with, as a means to read from standard input, to read from a file:
public static String readLineFromFile() throws FileNotFoundException {
Scanner scanner = new Scanner(new File("file.txt")); // throws java.io.FileNotFoundException
return scanner.nextLine();
}
FileNotFoundException
is a standard checked exception. This constructor of Scanner
declares that it may throw the FileNotFoundException
exception if the specified file does not exist. To ensure that this method can be compiled successfully, we must include the throws
keyword in the method declaration to indicate that the method may throw the FileNotFoundException
exception. As a result, the caller of this method will need to decide whether to handle the exception internally or throw it further to its caller method.
2. Unchecked exceptions are represented by the RuntimeException
class and all its subclasses. The compiler does not check whether the programmer expects the occurrence of such exceptions in a program.
Here is a method that throws a NumberFormatException
when the input string has an invalid format (for example: abc
).
public static Long convertStringToLong(String str) {
return Long.parseLong(str); // It may throw a NumberFormatException
}
The example in this block of code always successfully compiles without the throws
keyword in the declaration.
Runtime exceptions may occur anywhere in a program. The compiler doesn't require that you specify runtime exceptions in declarations. Adding them to each method's declaration would reduce the clarity of a program.
The Error
class and its subclasses are also considered as unchecked exceptions. However, they form a separate class.
Conclusion
Exceptions do not prevent a program from being compiled and run, but there is another issue - the program crashes as soon as the line with an exception is being executed.
You can use control statements to avoid some kinds of exceptions (like ArithmeticException
or NumberFormatException
) in your programs.
All exceptions are represented by the Throwable
class , which has two subclasses: Exception
and Error
. There are also two exception types: checked and unchecked.
Unchecked exceptions are not validated by the compiler, so you don't have to handle them. They are represented by the RuntimeException
subclass of the Exception
class. Errors from the Error
class are also considered unchecked.
Checked exceptions have to be handled and indicated explicitly. They are located in all the other subclasses of Exception
.