Java check error type

How can I determine which type of exception was caught, if an operation catches multiple exceptions? This example should make more sense: try { int x = doSomething(); } catch (NotAnInt | ParseE...

How can I determine which type of exception was caught, if an operation catches multiple exceptions?

This example should make more sense:

try {
  int x = doSomething();
} catch (NotAnInt | ParseError e) {
  if (/* thrown error is NotAnInt */) {    // line 5
    // printSomething
  } else {
    // print something else
  }
}

On line 5, how can I check which exception was caught?

I tried if (e.equals(NotAnInt.class)) {..} but no luck.

NOTE: NotAnInt and ParseError are classes in my project that extend Exception.

jww's user avatar

jww

95k88 gold badges397 silver badges861 bronze badges

asked Dec 3, 2014 at 20:23

jean's user avatar

2

If you can, always use separate catch blocks for individual exception types, there’s no excuse to do otherwise:

} catch (NotAnInt e) {
    // handling for NotAnInt
} catch (ParseError e) {
    // handling for ParseError
}

…unless you need to share some steps in common and want to avoid additional methods for reasons of conciseness:

} catch (NotAnInt | ParseError e) {
    // a step or two in common to both cases
    if (e instanceof NotAnInt) {
        // handling for NotAnInt
    } else  {
        // handling for ParseError
    }
    // potentially another step or two in common to both cases
}

however the steps in common could also be extracted to methods to avoid that ifelse block:

} catch (NotAnInt e) {
    inCommon1(e);
    // handling for NotAnInt
    inCommon2(e);
} catch (ParseError e) {
    inCommon1(e);
    // handling for ParseError
    inCommon2(e);
}

private void inCommon1(e) {
    // several steps
    // common to
    // both cases
}
private void inCommon2(e) {
    // several steps
    // common to
    // both cases
}

answered Dec 3, 2014 at 20:25

Erik Kaplun's user avatar

Erik KaplunErik Kaplun

36.4k15 gold badges99 silver badges109 bronze badges

3

If Multiple throws are happening in a single catch() then to recognize which Exception , you could use instanceof operator.

The java instanceof operator is used to test whether the object is an instance of the specified type (class or subclass or interface).

Try this code :-

        catch (Exception e) {
            if(e instanceof NotAnInt){

            // Your Logic.

            } else if  if(e instanceof ParseError){                

             //Your Logic.
            }
      }

answered Jul 26, 2018 at 12:22

anoopknr's user avatar

anoopknranoopknr

2,9872 gold badges21 silver badges32 bronze badges

4

Use multiple catch blocks, one for each exception:

try {
   int x = doSomething();
}
catch (NotAnInt e) {
    // print something
}
catch (ParseError e){
    // print something else
}

Worldcrafter's user avatar

answered Dec 3, 2014 at 20:26

DavidGSola's user avatar

DavidGSolaDavidGSola

6675 silver badges15 bronze badges

0

If anyone didn’t know what type of exception was thrown in the method e.g a method with a lot of possibilities like this one :

public void onError(Throwable e) {

}

You can get the exception class by

       Log.e("Class Name", e.getClass().getSimpleName());

In my case it was UnknownHostException

then use instanseof as mentioned in the previous answers to take some actions

public void onError(Throwable e) {
       Log.e("Class Name", e.getClass().getSimpleName());

       if (e instanceof UnknownHostException)
            Toast.makeText(context , "Couldn't reach the server", Toast.LENGTH_LONG).show();
       else 
          // do another thing
}

answered Jul 18, 2020 at 7:54

Mohammad's user avatar

MohammadMohammad

1,1761 gold badge14 silver badges25 bronze badges

Время прочтения
9 мин

Просмотры 270K

Это вторая часть статьи (первая часть — try-catch-finally), посвященной такому языковому механизму Java как исключения. Она имеет вводный характер и рассчитана на начинающих разработчиков или тех, кто только приступает к изучению языка.

Также я веду курс «Scala for Java Developers» на платформе для онлайн-образования udemy.com (аналог Coursera/EdX).

1. Магия checked/unchecked
2. Пессимистичный механизм
3. throws с непроверяемым (unckecked) исключением
4. Множественные исключения
5. Или catch, или throws
6. Поведение компилятора/JVM
7. Overriding и throws
8. Передача свойства по наследству

1. «Магия» checked/unchecked

Механизм исключительных ситуация в Java связан с двумя элементами «магии», т.е. поведения, которое никак не отражено в исходном коде:
1. «Магию» java.lang.Throwable — в throw, catch и throws могут стоять исключительно Throwable или его наследники (мы уже разбирали в предыдущей лекции). Это «право» находиться в throw, catch и throws никак не отражено в исходном коде.
2. Все исключительные ситуации делятся на «проверяемые» (checked) и «непроверяемые» (unchecked). Это свойство присуще «корневищу» (Throwable, Error, Exception, RuntimeException) и передается по наследству. Никак не видимо в исходном коде класса исключения.

В дальнейших примера просто учтите, что
— Throwable и Exception и все их наследники (за исключением наследников Error-а и RuntimeException-а) — checked
— Error и RuntimeException и все их наследники — unchecked

checked exception = проверяемое исключение, проверяемое компилятором.
Что именно проверяет компилятор мы разберем на этой лекции.

Напомним иерархию исключений

                    Object
                      |
                  Throwable
                  /      
              Error     Exception
                            |
                    RuntimeException

Расставим значение свойства checked/unchecked

                    Object
                      |
               Throwable(CHECKED)
               /            
     Error(UNCHECKED)    Exception(CHECKED)
                            |
                  RuntimeException(UNCHECKED)

2. Пессимистичный механизм

Я называю связь между проверяемыми исключениями и throws — «пессимистичной», польку мы можем «напугать» о большем, чем может произойти на самом деле, но не наоборот

Мы не можем бросать, но не предупредить

public class App {
    public static void main(String[] args) {
        throw new Exception(); // тут ошибка компиляции
    }
}

>> COMPILATION ERROR: unhandled exception: java.lang.Exception

Мы не можем бросать, но предупредить о «меньшем»

import java.io.IOException;

public class App {
    public static void main(String[] args) throws IOException {
        throw new Exception(); // тут ошибка компиляции
    }
}

>> COMPILATION ERROR: unhandled exception: java.lang.Exception

Мы можем предупредить точно о том, что бросаем

public class App {
    public static void main(String[] args) throws Exception { // предупреждаем о Exception
        throw new Exception(); // и кидаем Exception
    }
}

Мы можем предупредить о большем, чем мы бросаем

public class App {
    public static void main(String[] args) throws Throwable { // предупреждаем "целом" Throwable
        throw new Exception(); // а кидаем только Exception
    }
}

Можем даже предупредить о том, чего вообще нет

public class App {
    public static void main(String[] args) throws Exception { // пугаем
        // но ничего не бросаем
    }
}

Даже если предупреждаем о том, чего нет — все обязаны бояться

public class App {
    public static void main(String[] args) {
        f(); // тут ошибка компиляции
    }

    public static void f() throws Exception {
    }    
}

>> COMPILATION ERROR: unhandled exception: java.lang.Exception

Хотя они (испугавшиеся) могут перепугать остальных еще больше

public class App {
    // они пугают целым Throwable
    public static void main(String[] args) throws Throwable { 
        f();
    }
    // хотя мы пугали всего-лишь Exception
    public static void f() throws Exception {
    }    
}

В чем цель «пессимистичности»? Все достаточно просто.
Вы в режиме протипирования «набросали», скажем, класс-утилиту для скачивания из интернета

public class InternetDownloader {
    public static byte[] (String url) throws IOException { 
        return "<html><body>Nothing! It's stub!</body></html>".getBytes();
    }
}

и хотели бы «принудить» пользователей вашего класса УЖЕ ОБРАБАТЫВАТЬ возможное исключение IOException, хотя из реализации-пустышки вы ПОКА НЕ ГЕНЕРИРУЕТЕ такое исключение. Но в будущем — собираетесь.

3. throws с непроверяемым (unckecked) исключением

Вызов метода, который «пугает» unchecked исключением не накладывает на нас никаких обязанностей.

public class App {
    public static void main(String[] args) { 
        f();
    }
    public static void f() throws RuntimeException {
    }    
}

Эта конструкция служит цели «указать» программисту-читателю кода на то, что ваш метод может выбросить некоторое непроверяемое (unchecked) исключение.

Пример (java.lang.NumberFormatException — непроверяемое исключение):

package java.lang;

public final class Integer extends Number implements Comparable<Integer> {
    ...
    /**
     * ...
     *
     * @param s    a {@code String} containing the {@code int}
     *             representation to be parsed
     * @return     the integer value represented by the argument in decimal.
     * @exception  NumberFormatException  if the string does not contain a
     *               parsable integer.
     */
    public static int parseInt(String s) throws NumberFormatException {
        return parseInt(s,10);
    }
    ...
}

Integer.parseInt() может бросить unchecked NumberFormatException на неподходящем аргументе (int k = Integer.parseInt(«123abc»)), это отразили
— в сигнатуре метода: throws NumberFormatException
— в документации (javadoc): @ exception
но это ни к чему нас не обязывает.

4. Множественные исключения

Рассмотрим ситуацию с кодом, который может бросать проверяемые исключения разных типов.
Далее учитывайте, что EOFException и FileNotFoundException — потомки IOException.

Мы можем точно указать, что выбрасываем

import java.io.EOFException;
import java.io.FileNotFoundException;

public class App {
    // пугаем ОБОИМИ исключениями
    public static void main(String[] args) throws EOFException, FileNotFoundException {
        if (System.currentTimeMillis() % 2 == 0) {
            throw new EOFException();
        } else {
            throw new FileNotFoundException();
        }
    }
}

или вот так

import java.io.EOFException;
import java.io.FileNotFoundException;

public class App {
    // пугаем ОБОИМИ исключениями
    public static void main(String[] args) throws EOFException, FileNotFoundException {
        f0();
        f1();
    }
    public static void f0() throws EOFException {...}
    public static void f1() throws FileNotFoundException {...}
}

А можем «испугать» сильнее (предком обоих исключений)

import java.io.EOFException;
import java.io.FileNotFoundException;
import java.io.IOException;

public class App {
    // пугаем ПРЕДКОМ исключений
    public static void main(String[] args) throws IOException {
        if (System.currentTimeMillis() % 2 == 0) {
            throw new EOFException();
        } else {
            throw new FileNotFoundException();
        }
    }
}

или вот так

import java.io.EOFException;
import java.io.FileNotFoundException;

public class App {
    // пугаем ПРЕДКОМ исключений
    public static void main(String[] args) throws IOException {
        f0();
        f1();
    }
    public static void f0() throws EOFException {...}
    public static void f1() throws FileNotFoundException {...}
}

Можно и вот так: EOFException и FileNotFoundException «обобщаем до» IOException, а InterruptedException «пропускаем нетронутым» (InterruptedException — НЕ потомок IOException)

import java.io.EOFException;
import java.io.FileNotFoundException;

public class App {
    public static void main(String[] args) throws IOException, InterruptedException {
        f0();
        f1();
        f2();
    }
    public static void f0() throws EOFException {...}
    public static void f1() throws FileNotFoundException {...}
    public static void f2() throws InterruptedException {...}
}

5. Или catch, или throws

Не надо пугать тем, что вы перехватили
так

public class App {
    public static void main(String[] args) {
        try {
            throw new Exception();
        } catch (Exception e) {
            // ...
        }
    }
}

или так (ставим catch по предку и точно перехватываем)

public class App {
    public static void main(String[] args) {
        try {
            throw new Exception();
        } catch (Throwable e) {
            // ...
        }
    }
}

Но если перехватили только потомка, то не выйдет

public class App {
    public static void main(String[] args) {
        try {
            throw new Throwable();
        } catch (Exception e) {
            // ...
        }
    }
}

>> COMPILATION ERROR: unhandled exception: java.lang.Throwable

Не годится, естественно, и перехватывание «брата»

public class App {
    public static void main(String[] args) {
        try {
            throw new Exception();
        } catch (Error e) {
            // ...
        }
    }
}

>> COMPILATION ERROR: unhandled exception: java.lang.Exception

Если вы часть перехватили, то можете этим не пугать

import java.io.EOFException;
import java.io.FileNotFoundException;

public class App {
    // EOFException перехватили catch-ом, им не пугаем
    public static void main(String[] args) throws FileNotFoundException {
        try {
            if (System.currentTimeMillis() % 2 == 0) {
                throw new EOFException();
            } else {
                throw new FileNotFoundException();
            }
        } catch (EOFException e) {
            // ...
        }
    }
}

6. Поведение компилятора/JVM

Необходимо понимать, что
проверка на cheched исключения происходит в момент компиляции (compile-time checking)
перехват исключений (catch) происходит в момент выполнения (runtime checking)

public class App {
    // пугаем Exception
    public static void main(String[] args) throws Exception { 
        Throwable t = new Exception(); // и лететь будет Exception
        throw t; // но тут ошибка компиляции 
    }  
}

>> COMPILATION ERROR: unhandled exception: java.lang.Throwable

Полная аналогия с

public class App {
    public static void main(String[] args) throws Exception { 
        Object ref = "Hello!";  // ref указывает на строку 
        char c = ref.charAt(0); // но тут ошибка компиляции 
    }  
}

>> COMPILATION ERROR: Cannot resolve method 'charAt(int)'

Хотя ССЫЛКА ref УКАЗЫВАЕТ на объект типа java.lang.String (у которого имеется метод charAt(int)), но ТИП ССЫЛКИ — java.lang.Object (ссылка типа java.lang.Object на объект типа java.lang.String). Компилятор ориентируется на «левый тип» (тип ссылки, а не тип ссылаемого) и не пропускает такой код.

Хотя В ДАННОЙ СИТУАЦИИ компилятор мог бы разобрать, что t ссылается на Exception, а ref — на String, но этого уже невозможно сделать при раздельно компиляции. Представьте, что мы МОГЛИ БЫ скомпилировать ОТДЕЛЬНО такой класс, упаковать в jar и распространять

// НЕ КОМПИЛИРУЕТСЯ! ИССЛЕДУЕМ ГИПОТЕТИЧЕСКУЮ СИТУАЦИЮ!
public class App {
    public static void f0(Throwable t) throws Exception { 
        throw t;
    }  
    public static void f1(Object ref){ 
        char c = ref.charAt(0);
    }  
}

А кто-то берет этот класс, добавляет в classpath и вызывает App.f0(new Throwable()) или App.f1(new Integer(42)). В таком случае JVM столкнулась бы с ситуацией, когда от нее требует бросить проверяемое исключение, которое не отследил компилятор (в случае с f0) или вызвать метод, которого нет (в случае с f1)!

Java — язык со статической типизацией (т.е. отслеживание корректности использования типов (наличие используемых полей, наличие вызываемых методов, проверка на checked исключения, …) проводится компилятором), запрещает такое поведение. В некоторых языках (языки с динамической типизацией — отслеживание корректности использования типов проводится средой исполнения (оно разрешено, например в JavaScript).

Компилятор не пропустит этот код, хотя метод main ГАРАНТИРОВАННО НЕ ВЫБРОСИТ ИСКЛЮЧЕНИЯ

public class App {
    // пугаем Exception
    public static void main(String[] args) throws Exception { 
        try {
            Throwable t = new Exception(); // и лететь будет Exception
            throw t; // но тут ошибка компиляции 
        } catch (Exception e) {
            System.out.println("Перехвачено!");
        }
    }  
}

>> COMPILATION ERROR: unhandled exception: java.lang.Throwable
public class App {
    // ТЕПЕРЬ пугаем Throwable
    public static void main(String[] args) throws Throwable { 
        try {
            Throwable t = new Exception(); // а лететь будет Exception
            throw t;
        } catch (Exception e) { // и мы перехватим Exception
            System.out.println("Перехвачено!");
        }
    }  
}

>> Перехвачено!

7. Overriding и throws

При переопределении (overriding) список исключений потомка не обязан совпадать с таковым у предка.
Но он должен быть «не сильнее» списка предка:

import java.io.FileNotFoundException;
import java.io.IOException;

public class Parent {
    // предок пугает IOException и InterruptedException
    public void f() throws IOException, InterruptedException {}
}

class Child extends Parent {
    // а потомок пугает только потомком IOException
    @Override
    public void f() throws FileNotFoundException {}
}

Однако тут мы попытались «расширить тип» бросаемых исключений

import java.io.IOException;

public class Parent {
    public void f() throws IOException, InterruptedException {}
}

class ChildB extends Parent {
    @Override
    public void f() throws Exception {}
}

>> COMPILATION ERROR: overridden method does not throw 'java.lang.Exception'

Почему можно сужать тип, но не расширять?
Рассмотрим следующую ситуацию:

public class Parent {
    // предок пугает Exception
    public void f() throws Exception {}
}
// тут ошибка компиляции в Java, но, ДОПУСТИМ, ее нет
public class Child extends Parent {
    // потомок расширил Exception до Throwable
    public void f() throws Throwable {}
}
public class Demo {
    public static void test(Parent ref) {
        // тут все компилируется, Parent.f() пугает Exception и мы его ловим catch
        try {
            ref.f();
        } catch(Exception e) {}
    }
}
public class App {
    public static void main(String[] args) {
        // тут все компилируется, Demo.test хотел Parent и мы дали ему подтип - Child
        Demo.test(new Child());
    }
}

Внимательно посмотрите на этот пример — если бы потомок мог расширять тип бросаемого исключения предка, то те места которые «ждут» предка, а получают экземпляр «расширенного» потомка могли бы неконтролируемо выбрасывать проверяемые исключения

8. Передача свойства по наследству

Напомним иерархию исключений с расставленными флагами свойства checked/unchecked

                    Object
                      |
               Throwable(CHECKED)
               /            
     Error(UNCHECKED)    Exception(CHECKED)
                            |
                  RuntimeException(UNCHECKED)

Логика расположения свойства НЕ СВЯЗАНА С НАСЛЕДОВАНИЕМ. Эту логику мы рассмотрим позже (в следующих статьях).
Однако свойство checked/unchecked пользовательских классов исключений строится ИСКЛЮЧИТЕЛЬНО НА ОСНОВЕ НАСЛЕДОВАНИЯ.
Правило крайне простое:
1. Если исключение из списка Throwable, Error, Exception, RuntimeException — то твое свойство надо просто запомнить.
2. Если ты не из списка, то твое свойство равно свойству предка. Нарушить наследование тут нельзя.

Если мы породим потомков A, B, C, D, E, F, G, H, I, J, K, L которые следующим образом наследуются от «корневища» (Throwable, Error, Exception, RuntimeException), то значение их свойства checked/unchecked можно увидеть на схеме

                    Object
                      |
               Throwable(CHECKED)
               /    |       
 Error(UNCHECKED)   |   Exception(CHECKED)
    |       |       |      |            |
    A(UNC)  D(UNC)  |      F(C)        RuntimeException(UNCHECKED)
  /                |     /                |       |
B(UNC) C(UNC)       |   G(C)  H(C)          I(UNC)  J(UNC)  
                   E(C)                   /    
                                       K(UNC) L(UNC)

Контакты

Я занимаюсь онлайн обучением Java (курсы программирования) и публикую часть учебных материалов в рамках переработки курса Java Core. Видеозаписи лекций в аудитории Вы можете увидеть на youtube-канале, возможно видео канала лучше систематизировано в этой статье.
Мой метод обучения состоит в том, что я

  1. показываю различные варианты применения
  2. строю усложняющуюся последовательность примеров по каждому варианту
  3. объясняю логику двигавшую авторами (по мере возможности)
  4. даю большое количество тестов (50-100) всесторонне проверяющее понимание и демонстрирующих различные комбинации
  5. даю лабораторные для самостоятельной работы

Данная статье следует пунктам #1 (различные варианты) и #2(последовательность примеров по каждому варианту).

skype: GolovachCourses
email: GolovachCourses@gmail.com

Exception Handling in Java is one of the effective means to handle the runtime errors so that the regular flow of the application can be preserved. Java Exception Handling is a mechanism to handle runtime errors such as ClassNotFoundException, IOException, SQLException, RemoteException, etc.

Exception is an unwanted or unexpected event, which occurs during the execution of a program, i.e. at run time, that disrupts the normal flow of the program’s instructions. Exceptions can be caught and handled by the program. When an exception occurs within a method, it creates an object. This object is called the exception object. It contains information about the exception, such as the name and description of the exception and the state of the program when the exception occurred.

Major reasons why an exception Occurs

  • Invalid user input
  • Device failure
  • Loss of network connection
  • Physical limitations (out of disk memory)
  • Code errors
  • Opening an unavailable file

Errors represent irrecoverable conditions such as Java virtual machine (JVM) running out of memory, memory leaks, stack overflow errors, library incompatibility, infinite recursion, etc. Errors are usually beyond the control of the programmer, and we should not try to handle errors.

Let us discuss the most important part which is the differences between Error and Exception that is as follows: 

  • Error: An Error indicates a serious problem that a reasonable application should not try to catch.
  • Exception: Exception indicates conditions that a reasonable application might try to catch.

Exception Hierarchy

All exception and error types are subclasses of class Throwable, which is the base class of the hierarchy. One branch is headed by Exception. This class is used for exceptional conditions that user programs should catch. NullPointerException is an example of such an exception. Another branch, Error is used by the Java run-time system(JVM) to indicate errors having to do with the run-time environment itself(JRE). StackOverflowError is an example of such an error.

Exception Hierarchy in Java

Types of Exceptions 

Java defines several types of exceptions that relate to its various class libraries. Java also allows users to define their own exceptions.

Types of Exceptions

Exceptions can be categorized in two ways:

  1. Built-in Exceptions
    • Checked Exception
    • Unchecked Exception 
  2. User-Defined Exceptions

Let us discuss the above-defined listed exception that is as follows:

A. Built-in Exceptions:

Built-in exceptions are the exceptions that are available in Java libraries. These exceptions are suitable to explain certain error situations.

  • Checked Exceptions: Checked exceptions are called compile-time exceptions because these exceptions are checked at compile-time by the compiler.
     
  • Unchecked Exceptions: The unchecked exceptions are just opposite to the checked exceptions. The compiler will not check these exceptions at compile time. In simple words, if a program throws an unchecked exception, and even if we didn’t handle or declare it, the program would not give a compilation error.

Note: For checked vs unchecked exception, see Checked vs Unchecked Exceptions 

B. User-Defined Exceptions:

Sometimes, the built-in exceptions in Java are not able to describe a certain situation. In such cases, users can also create exceptions, which are called ‘user-defined Exceptions’. 

The advantages of Exception Handling in Java are as follows:

  1. Provision to Complete Program Execution
  2. Easy Identification of Program Code and Error-Handling Code
  3. Propagation of Errors
  4. Meaningful Error Reporting
  5. Identifying Error Types

Methods to print the Exception information:

1.printStackTrace()– This method prints exception information in the format of Name of the exception: description of the exception, stack

trace.

Java

import java.io.*;

class GFG {

    public static void main (String[] args) {

      int a=5;

      int b=0;

        try{

          System.out.println(a/b);

        }

      catch(ArithmeticException e){

        e.printStackTrace();

      }

    }

}

Output:

java.lang.ArithmeticException: / by zero
at GFG.main(File.java:10)

2.toString() – This method prints exception information in the format of Name of the exception: description of the exception.

Java

import java.io.*;

class GFG1 {

    public static void main (String[] args) {

      int a=5;

      int b=0;

        try{

          System.out.println(a/b);

        }

      catch(ArithmeticException e){

        System.out.println(e.toString());

      }

    }

}

Output:

java.lang.ArithmeticException: / by zero

3.getMessage() -This method prints only the description of the exception.

Java

import java.io.*;

class GFG1 {

    public static void main (String[] args) {

      int a=5;

      int b=0;

        try{

          System.out.println(a/b);

        }

      catch(ArithmeticException e){

        System.out.println(e.getMessage());

      }

    }

}

Output:

/ by zero

How Does JVM handle an Exception?

Default Exception Handling: Whenever inside a method, if an exception has occurred, the method creates an Object known as an Exception Object and hands it off to the run-time system(JVM). The exception object contains the name and description of the exception and the current state of the program where the exception has occurred. Creating the Exception Object and handling it in the run-time system is called throwing an Exception. There might be a list of the methods that had been called to get to the method where an exception occurred. This ordered list of the methods is called Call Stack. Now the following procedure will happen. 

  • The run-time system searches the call stack to find the method that contains a block of code that can handle the occurred exception. The block of the code is called an Exception handler.
  • The run-time system starts searching from the method in which the exception occurred, and proceeds through the call stack in the reverse order in which methods were called.
  • If it finds an appropriate handler, then it passes the occurred exception to it. An appropriate handler means the type of the exception object thrown matches the type of the exception object it can handle.
  • If the run-time system searches all the methods on the call stack and couldn’t have found the appropriate handler, then the run-time system handover the Exception Object to the default exception handler, which is part of the run-time system. This handler prints the exception information in the following format and terminates the program abnormally.
Exception in thread "xxx" Name of Exception : Description
... ...... ..  // Call Stack

Look at the below diagram to understand the flow of the call stack. 

call stack

Illustration:

Java

class GFG {

    public static void main(String args[])

    {

        String str = null;

        System.out.println(str.length());

    }

}

Output:

Let us see an example that illustrates how a run-time system searches for appropriate exception handling code on the call stack.

Example:

Java

class GFG {

    static int divideByZero(int a, int b)

    {

        int i = a / b;

        return i;

    }

    static int computeDivision(int a, int b)

    {

        int res = 0;

        try {

            res = divideByZero(a, b);

        }

        catch (NumberFormatException ex) {

            System.out.println(

                "NumberFormatException is occurred");

        }

        return res;

    }

    public static void main(String args[])

    {

        int a = 1;

        int b = 0;

        try {

            int i = computeDivision(a, b);

        }

        catch (ArithmeticException ex) {

            System.out.println(ex.getMessage());

        }

    }

}

How Programmer Handles an Exception?

Customized Exception Handling: Java exception handling is managed via five keywords: try, catch, throw, throws, and finally. Briefly, here is how they work. Program statements that you think can raise exceptions are contained within a try block. If an exception occurs within the try block, it is thrown. Your code can catch this exception (using catch block) and handle it in some rational manner. System-generated exceptions are automatically thrown by the Java run-time system. To manually throw an exception, use the keyword throw. Any exception that is thrown out of a method must be specified as such by a throws clause. Any code that absolutely must be executed after a try block completes is put in a finally block.

Tip: One must go through control flow in try catch finally block for better understanding.  

Need for try-catch clause(Customized Exception Handling)

Consider the below program in order to get a better understanding of the try-catch clause.

Example:

Java

class GFG {

    public static void main(String[] args)

    {

        int[] arr = new int[4];

        int i = arr[4];

        System.out.println("Hi, I want to execute");

    }

}

Output: 

Output explanation: In the above example, an array is defined with size i.e. you can access elements only from index 0 to 3. But you trying to access the elements at index 4(by mistake) that’s why it is throwing an exception. In this case, JVM terminates the program abnormally. The statement System.out.println(“Hi, I want to execute”); will never execute. To execute it, we must handle the exception using try-catch. Hence to continue the normal flow of the program, we need a try-catch clause. 

How to Use the try-catch Clause?

try {
    // block of code to monitor for errors
    // the code you think can raise an exception
} catch (ExceptionType1 exOb) {
    // exception handler for ExceptionType1
} catch (ExceptionType2 exOb) {
    // exception handler for ExceptionType2
}
// optional
finally {  // block of code to be executed after try block ends 
}

Certain below key points are needed to be remembered that are as follows:   

  • In a method, there can be more than one statement that might throw an exception, So put all these statements within their own try block and provide a separate exception handler within their own catch block for each of them.
  • If an exception occurs within the try block, that exception is handled by the exception handler associated with it. To associate the exception handler, we must put a catch block after it. There can be more than one exception handlers. Each catch block is an exception handler that handles the exception to the type indicated by its argument. The argument, ExceptionType declares the type of exception that it can handle and must be the name of the class that inherits from the Throwable class.
  • For each try block, there can be zero or more catch blocks, but only one final block.
  • The finally block is optional. It always gets executed whether an exception occurred in try block or not. If an exception occurs, then it will be executed after try and catch blocks. And if an exception does not occur, then it will be executed after the try block. The finally block in java is used to put important codes such as clean up code e.g., closing the file or closing the connection.

The summary is depicted via visual aid below as follows: 

Related Articles:  

  • Types of Exceptions in Java
  • Checked vs Unchecked Exceptions
  • Throw-Throws in Java

This article is contributed by Nitsdheerendra and Gaurav Miglani. If you like GeeksforGeeks and would like to contribute, you can also write an article using write.geeksforgeeks.org or mail your article to review-team@geeksforgeeks.org. See your article appearing on the GeeksforGeeks main page and help other Geeks. Please write comments if you find anything incorrect, or you want to share more information about the topic discussed above. 

Related Courses

Java Programming Foundation – Self Paced Course

Find the right course for you to start learning Java Programming Foundation from the industry experts having years of experience. This Java Programming Foundation – Self Paced Course covers the fundamentals of the Java programming language, data types, operators and flow control, loops, strings, and much more. No more waiting! Start Learning JAVA Now and Become a Complete Java Engineer!

Из этой статьи вы узнаете:

  • как пробросить исключение наверх;
  • когда и зачем это делать;
  • о checked- и unchecked-исключениях;
  • о хоткеях, ускоряющих обработку исключений.

А сперва советуем перечитать первую часть.

Ранее мы рассказали, что все исключения в Java — это классы, наследники Throwable и Exception; мельком взглянули на класс Error, от которого наследуются ошибки уровня Java-машины.

Верхушка иерархии исключений Java. Схема: Мария Помазкина для Skillbox Media

Сегодня поговорим только про семейство Exception.

В Java много встроенных исключений. На каждом останавливаться не будем: вы и так познакомитесь с ними на практике. Сейчас важнее до конца уяснить, как с исключениями работать.

Напомню, что есть три способа обойтись с исключением:

  1. Ничего не делать.
  2. Обработать в конструкции try-catch.
  3. Пробросить наверх.

С первыми двумя мы разбирались здесь. Остался проброс исключений наверх.

Наверх — это куда? Это выше по стеку вызовов. То есть если метод A вызвал метод B, метод B вызвал метод C, а методC пробросил исключение наверх, то оно всплывёт до метода B — и станет его проблемой.

Как пробросить исключение?

Проще простого.

Вернёмся к примеру из предыдущей статьи. Помните, там был метод hereWillBeTrouble, который мог вызвать исключение ArithmeticException?

Чтобы пробросить это исключение, достаточно добавить в сигнатуру метода hereWillBeTrouble ключевое слово throws, а после него — название класса пробрасываемого исключения.

Вот так:

public static void main(String[] args) {
    hereWillBeTrouble();
}

private static void hereWillBeTrouble() throws ArithmeticException {
    System.out.println("Всё, что было до...");
    int oops = 42 / 0;
    System.out.println("Всё, что будет после...");
}

Теперь метод hereWillBeTrouble объявляет всему миру, что может кинуть исключение ArithmeticException.

Посмотрим, что произойдёт, если он исполнит эту угрозу.

Изначально исключение возникнет напротив стрелки 1. Но throws передаст его в строку, на которую указывает стрелка 2.

Скриншот: Мария Помазкина для Skillbox Media

Исключение передалось как эстафетная палочка. И теперь источником исключения является не только строка int oops = 42 / 0, но и строка hereWillBeTrouble();.

Что нам дал проброс исключения?

  1. Так мы говорим разработчику, который вызывает метод hereWillBeTrouble, что в нём может возникнуть исключение типа ArithmeticException.
  2. В вызывающем метод hereWillBeTrouble коде это исключение можно обработать с помощью конструкции try-catch.

public static void main(String[] args) {
    try {
        hereWillBeTrouble();
    } catch (ArithmeticException ex) {
        System.out.println("Да, это случилось");
    }
}

private static void hereWillBeTrouble() throws ArithmeticException {
    System.out.println("Всё, что было до...");
    int oops = 42 / 0;
    System.out.println("Всё, что будет после...");
}

Вернёмся к первому блоку кода (в котором нет конструкции try-catch).

Вы наверняка заметили, что и без throws код работал точно так же. Стектрейс (разбирали его здесь) не изменился, исключение в итоге бросалось пользователю.

Но теперь разработчики, которые используют метод hereWillBeTrouble, предупреждены и, если нужно, обезопасят свой код с помощью try-catch.

И это всё, что дало слово throws?

В случае с непроверяемыми исключениями — да. Но вот в случае с проверяемыми… пристегнитесь, нас ждёт новый поворот в деле об исключениях!

Все исключения в Java делятся на две группы. Зовутся они checked и unchecked («проверяемые» и «непроверяемые»). Иногда их также называют «обрабатываемые» и «необрабатываемые».

Запомните! Проверяемые исключения обязательно нужно обрабатывать либо пробрасывать. Непроверяемые — по желанию.

Как понять, проверяемое исключение или нет?

Разработчики языка Java решили так: исключения, которые наследуются от RuntimeException, — непроверяемые, а все остальные — проверяемые.

Исследуем различия на практике

Раз ArithmeticException — непроверяемое исключение, то его можно обрабатывать, а можно и не обрабатывать. Мы обрабатывали.

Когда при выполнении кода возникало исключение — программа вылетала, это само собой. Но заметьте: программа компилировалась и запускалась даже с риском возникновения исключения.

Без проблем компилируется и код, в котором кидают исключение явно:

private static void hereWillBeTrouble(int a, int b) {
    if (b == 0) {
        throw new ArithmeticException("Ты опять делишь на ноль?");
    }
    int oops = a / b;
    System.out.println(oops);
}

Компиляция успешна только потому, что исключение непроверяемое.

А теперь проделаем то же самое с проверяемым исключением:

public static void main(String[] args) {
    hereWillBeTrouble();
}

private static void hereWillBeTrouble() {
    throw new CloneNotSupportedException();
}

Вжух — и наш код перестал компилироваться!

Вот мы и подтвердили разницу между проверяемыми и непроверяемыми исключениями: нельзя скомпилировать и запустить программу, если в ней есть проверяемое исключение, которое кинули, но не обработали и не пробросили.

Итак, вариантов у нас два:

  • обработать исключение с помощью try-catch;
  • пробросить исключение выше.

Оборачивать код конструкцией try-catch вы уже умеете. Рассмотрим второй вариант:

public static void main(String[] args) {
    hereWillBeTrouble();
}

private static void hereWillBeTrouble() throws CloneNotSupportedException {
    throw new CloneNotSupportedException();
}

А код всё ещё не компилируется. Но если в первом случае ошибка компиляции возникала в методе hereWillBeTrouble — в строке, где кидалось проверяемое, но при этом необработанное исключение, то теперь ошибка будет в методе main, потому что проверяемое и необработанное исключение поднялось до него.

Теперь исключение возникает в строке, где вызывается метод hereWillBeTrouble. Повторимся, так происходит потому, что этот метод кидает проверяемое исключение (он продекларировал это с помощью ключевого слова throws).

И теперь методу main тоже нужно что-то с исключением делать: либо обработать его с помощью try-catch, либо пробросить ещё выше.

Первый вариант, try-catch:

public static void main(String[] args) {
    try {
        hereWillBeTrouble();
    } catch (CloneNotSupportedException e) {
        e.printStackTrace();
    }
}

Второй вариант, проброс выше:

public static void main(String[] args) throws CloneNotSupportedException {
    hereWillBeTrouble();
}

Несмотря на то что метод main запускается первым, ему тоже разрешено кидать исключения.

Что мы выяснили про проброс исключения наверх?

В случае с непроверяемыми исключениями это способ передать свою проблему тем, кто будет вызывать наш метод.

Наш метод теперь компилируется, а вот вызывающий — нет. Так будет до тех пор, пока в нём не сделают что-то с исключением (возможно, передадут его ещё выше по стеку вызовов).

Интересный момент. Если метод способен кинуть проверяемое исключение, то компилятор не будет перепроверять: ему всё равно, что происходит внутри этого метода. Для него главное, что метод о такой возможности заявил.

Убедитесь, что следующий код не скомпилируется:

public static void main(String[] args) {
    hereWillBeTrouble();
}

private static void hereWillBeTrouble() throws CloneNotSupportedException {
    System.out.println("Я не буду ничего кидать, не просите");
}

Напоследок — небольшая подсказка.

Среда разработки стремится упростить нам жизнь: когда в коде обнаруживается проверяемое исключение — она предлагает реализовать его обработку.

Нам остаётся только выбрать, чего мы хотим: обработать исключение с помощью try-catch или пробросить выше.

Горячие клавиши, которые позволяют это сделать:

  • в среде IntelliJ IDEA — Alt + Enter / ⌥Enter,
  • для среды разработки Eclipse — F2.

Например:

IntelliJ IDEA:

Скриншот: Мария Помазкина для Skillbox Media

Eclipse:

Скриншот: Мария Помазкина для Skillbox Media

В следующей статье я расскажу обо всей мощи конструкции try-catch.

Ещё больше хитростей, дженериков и других особенностей Java — на курсе «Профессия Java-разработчик». Научим программировать на самом востребованном языке и поможем устроиться на работу.


Errors

tutorial
java


  • Compiler Errors
  • Runtime Errors
    • Stack Traces
  • Catching Exceptions
    • Checked Exceptions
  • Logic Errors
  • Debugging
  • General Tips
  • Homework

By now, you’ve probably seen a few errors, either when compiling or running your code. They can be frustrating, but they can also give you a lot of information about exactly how you can fix the problems in your code. This tutorial covers the types of errors you’ll see when programming in Java, and how to fix them.

Compiler Errors

Remember that the compiler converts from Java code (which humans can read) to Java byte code (which computers can read). The Java compiler is a lot like a translater. Now imagine that you told a translator to translate “flibberflabby” from English to Spanish. The translator would tell you that “flibberflabby” isn’t a word, so it can’t be translated!

That’s what happens when you write code that the compiler doesn’t understand. It doesn’t know how to translate your code into Java byte code, so it gives you an error. The rules of a language are called its syntax. Compiler errors are also called syntax errors, because it means that your code broke the rules of the langauge.

Compiler errors can be for things like forgotten semicolons or misspelled variables, but they can also be for violating the rules of Java, like using a non-static variable from a static function.

Here’s an example that contains a syntax error. Can you spot it?

class BadExample(){
	public static void main(String[] args){
		System.out.println("Happy Coding!");
	}
}

Do you see the syntax error? Try to compile this code. Save it to a file named BadExample.java, and then open a command prompt to the directory that contains that file, then type javac BadExample.java and press enter.

The compiler will show you this error:

> javac BadExample.java

BadExample.java:1: error: '{' expected
class BadExample(){
                ^
1 error

This might seem like gibberish, but there’s a ton of information packed in here:

  • The BadExample.java part tells you which class contains the error. This is useful if you’re compiling multiple classes.
  • The :1 part tells you which line of code has the error. In this case, the first line of code contains an error. Note that sometimes an error can impact multiple lines, so if you can’t spot an error, try looking at the lines just before the reported error.
  • The error: '{' expected tells you why the compiler can’t translate your code. In this case, it’s saying that it expected a curly bracket { character.
  • The ^ points to exactly where in the line of code the error was found. In this case, it’s pointing to the opening parenthesis ( character.

So, this error is telling us that the compiler thought it would see a curly bracket {, but it saw a parenthesis ( instead. This is a violation of the syntax for creating a class, which shouldn’t have () parentheses in the class declaration!

Now we know enough to fix our code:

class BadExample{
	public static void main(String[] args){
		System.out.println("Happy Coding!");
	}
}

Notice that we got rid of the parentheses () in the first line. This code will compile and run just fine.

It’s also possible to have more than one compiler error at a time. When this happens, always start with the first error. Fix that, and then try to compile again. Sometimes one syntax error can lead to multiple compiler errors. You should also make sure that you’ve split your problem up and you’re working on one small step. You don’t want to write 100 lines of code before compiling. Write a couple lines of code at a time, and compile as often as possible so you find errors as soon as you make them.

Runtime Errors

Even if you don’t get any compiler errors and the compiler translates your .java file into a .class file that you can run, your code still might contain other errors that the compiler can’t detect. Here’s an example:

public class ArgsPrinter{
	public static void main(String[] args){
		System.out.println(args[99]);
	}
}

This code prints the 100th argument from the args array (remember arrays are 0 based, so index 99 is the 100th index). It will compile fine, because the compiler has no way of knowing how many arguments you’re going to give your program until you actually run it.

But what happens if you run it but only give it 3 arguments?

> java ArgsPrinter
>java ArgsPrinter one two three
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 99
        at ArgsPrinter.main(ArgsPrinter.java:3)

You get a runtime error, which means that even though your code “looks” okay, it did something bad when it actually ran. Let’s look at the error message in more detail:

  • The Exception in thread "main" part tells us that we got a runtime error.
  • The java.lang.ArrayIndexOutOfBoundsException part tells you what type of error occurred. This is a class name that you can look up in the Java API. In this case, we’re getting an ArrayIndexOutOfBoundsException, which the Java API tells us means that “the index is either negative or greater than or equal to the size of the array.”
  • The 99 part gives you more information about the error. In this case it’s telling us that we tried to access the 99th index of an array.
  • The at ArgsPrinter.main(ArgsPrinter.java:3) part is called a stack trace, and it tells us which class, function, and line number the error occured on. In this case, the error occured on line 3 of the ArgsPrinter class, inside the main() function.

In this case the error was caused by accessing an index that the array didn’t have. We only gave the program 3 arguments, so the index only had 3 indexes. Then we tried to access index 99, which caused an error.

Stack Traces

Let’s look at another example that throws a runtime exception. Can you spot the error before running the code?

import java.util.ArrayList;

public class RuntimeErrorExample{

	private ArrayList<String> list = new ArrayList<String>();
	
	public void printLastThing(){
		int lastIndex = list.size() - 1;
		String thing = list.get(lastIndex);
		System.out.println(thing);
	}
	
	public static void main(String[] args){
		RuntimeErrorExample example = new RuntimeErrorExample();
		example.printLastThing();
	}
}

This code defines a RuntimeErrorExample class that contains one ArrayList variable named list. It also defines a function named printLastThing() that prints the last item in the ArrayList. The logic is correct: like arrays, ArrayList objects are 0 based, so if an ArrayList contains 5 items, the last item is at index 4. So getting its size and subtrating 1 will give us the last index.

The main() function then creates an instance of the class and calls the printLastThing() function. But when we run it, we get this runtime error:

>java RuntimeErrorExample
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: -1
        at java.util.ArrayList.elementData(ArrayList.java:418)
        at java.util.ArrayList.get(ArrayList.java:431)
        at RuntimeErrorExample.printLastThing(RuntimeErrorExample.java:9)
        at RuntimeErrorExample.main(RuntimeErrorExample.java:15)

We’re getting an ArrayIndexOutOfBoundsException just like before, and we can see that we’re trying to access index -1, which causes an error because the index can’t be negative.

Notice that the stack trace now contains 4 lines. You might see that the error is actually happening inside the ArrayList class. Before you assume you found a bug in the ArrayList class, let’s take a closer look at the rest of the stack trace!

The stack trace shows you the sequence of events that lead to to the error happening. To make sense of a stack trace, read it from the bottom to the top:

  • at RuntimeErrorExample.main(RuntimeErrorExample.java:15): This tells us that the code started out in the main() function, and that it got to line 15 before calling another function.
  • at RuntimeErrorExample.printLastThing(RuntimeErrorExample.java:9): Then the main() function called the printLastThing() function. The printLastThing() function got to line 9 of the RuntimeErrorExample class before calling another function.
  • at java.util.ArrayList.get(ArrayList.java:431): The printLastThing() function then called the get() function in the ArrayList class. Inside the ArrayList class, the get() function got to line 431 before calling another function.
  • at java.util.ArrayList.elementData(ArrayList.java:418): Finally, line 418 of the ArrayList class, inside the elementData() function, the error actually happens.

So, even though the error is coming from inside the ArrayList class, we can use the stack trace to work backwards and find out where in our code the cause of the error came from.

In this example, the error is happening because we never actually add anything to the ArrayList, so its size is 0. Then we subtract 1, which gives us -1, and we use that as an index. That causes the error.

Catching Exceptions

We could prevent the ArrayIndexOutOfBoundsException by using an if statement that checks to make sure the index is valid before using it. Or we could use the isEmpty() function that ArrayList gives us:

import java.util.ArrayList;

public class RuntimeErrorExample{

	private ArrayList<String> list = new ArrayList<String>();
	
	public void printLastThing(){
		if(list.isEmpty()){
			System.out.println("You forgot to add something to the ArrayList.");
		}
		else{
			int lastIndex = list.size() - 1;
			String thing = list.get(lastIndex);
			System.out.println(thing);
		}
	}
	
	public static void main(String[] args){
		RuntimeErrorExample example = new RuntimeErrorExample();
		example.printLastThing();
	}
}

Now when we call the printLastThing() function, we first check if the list is empty, and print a friendly message if it is. Then only if it’s not empty, we get the last index and print out the last item.

It’s always a good idea to idiot-proof your code like this.

Another way to protect your code from exceptions is using try and catch blocks. You put code that might cause an error inside a try block, and you put code you want to happen in case of an error inside a catch block. It looks like this:

try{
	//dangerous code here
}
//exception that might happen here
catch(Exception e){
	//code you want to run when an error happens
}

Here’s the above example again, using a try and catch block instead of an if statement:

import java.util.ArrayList;

public class RuntimeErrorExample{

	private ArrayList<String> list = new ArrayList<String>();
	
	public void printLastThing(){
		
		try{
			int lastIndex = list.size() - 1;
			
			//this line throws an exception
			String thing = list.get(lastIndex); 
			
			//this line won't be called!
			System.out.println(thing);
		}
		catch(ArrayIndexOutOfBoundsException e){
			//the program "shortcuts" to this code when an exception happens
			System.out.println("You forgot to add something to the ArrayList.");
		}
	}
	
	public static void main(String[] args){
		RuntimeErrorExample example = new RuntimeErrorExample();
		example.printLastThing();
	}
}

Now, when the String thing = list.get(lastIndex); line causes an ArrayIndexOutOfBoundsException, the program automatically jumps to the code inside the catch block. This means that the System.out.println(thing); line is not run. Instead, the code inside the catch block is run instead.

Also note that the code inside the catch block is only run when an exception is thrown. If no exception is thrown, then the code inside the catch block is skipped.

Also note that the catch block is given an instance of a class representing the error that occurred. You should check the Java API for useful function, but one you’ll use all the time is printStackTrace():

catch(ArrayIndexOutOfBoundsException e){
	System.out.println("You forgot to add something to the ArrayList.");
	e.printStackTrace();
}

This lets you add a friendly error message, but still prints the stack trace to the console so you can figure out exactly what caused the error.

Checked Exceptions

In the above code, note that we could use a try and catch block, but we didn’t have to. That’s because ArrayIndexOutOfBoundsException is an unchecked exception. If a function might throw an unchecked exception, you don’t have to do anything special.

However, functions can also throw checked exceptions, which you do have to catch. Look at this program:

import java.io.PrintWriter;

public class FileMaker{

	public static void main(String[] args){
		PrintWriter output = new PrintWriter("OutputFile.txt");
		output.write("Happy Coding!");
		output.close();
	}
}

This code uses the PrintWriter class to create a file named OutputFile.txt. First, we create an instance of PrintWriter and give it the name of a file to create. Then we write the contents of the file, and finally we call the close() function to free the file up (otherwise we might get errors when we try to open the file in a text editor). As always, you should check out the Java API to learn more about new classes.

But if you try to compile this code, you’ll get an error:

> javac FileMaker.java
FileMaker.java:6: error: unreported exception FileNotFoundException; must be caught or declared to be thrown
                PrintWriter output = new PrintWriter("OutputFile.txt");
                                     ^
1 error

This error tells us that the PrintWriter output = new PrintWriter("OutputFile.txt"); line of code can give us a FileNotFoundException, so we have to use a try and catch block with this code. That’s because FileNotFoundException is a checked exception, so we have to specifically catch possible errors.

So, we can put our code inside a try block, and we add a catch block that catches the FileNotFoundException. We just call the printStackTrace() function so we can debug the error if it ever happens.

import java.io.PrintWriter;
import java.io.FileNotFoundException;

public class FileMaker{

	public static void main(String[] args){
		try{
			PrintWriter output = new PrintWriter("OutputFile.txt");
			output.write("Happy Coding!");
			output.close();
		}
		catch(FileNotFoundException e){
			e.printStackTrace();
		}
	}
}

Now we’re specifically catching the FileNotFoundException, so the compiler is happy and our code is safe.

Note: Never leave a catch block empty! You’ll have no way to know that anything went wrong, which makes it very hard to debug your code when something does go wrong. And you’ve probably learned by now that things do go wrong all the time when you’re programming! So at least put a call to printStackTrace() inside a catch block, even if you don’t think the exception will ever actually happen.

Logic Errors

Sometimes you’ll write code that compiles, and doesn’t throw any runtime exceptions, but still doesn’t work how you expected it to. This is almost always caused by a logic error, and it’s usually a result of the programmer (that’s you!) making an assumption or a typo.

Let’s look at this program:

public class ArrayCounter{

	public static int countArray(int[] array){
		int total = 0;
		for(int i = 1; i < array.length; i++){
			total += array[i];
		}
		return total;
	}
	
	public static void main(String[] args){
		int[] array = {1, 2, 3};
		int total = countArray(array);
		System.out.println("The total is: " + total);
	}
}

This program creates a countArray() function that takes an int[] array parameter. It loops over the array and adds every number to the total, which it then returns. Then the main() function creates an array, calls the countArray() function with it, and then prints out the resulting total.

If you compile and run this program, you’ll see that it prints out The total is: 5, even though the total should be 6! Can you spot the problem?

The problem is this line of code:

for(int i = 1; i < array.length; i++){

Remember that arrays are 0-based, so the first index is always 0, not 1. So this loop actually starts at the second index, and it skips the first number! To fix the logic error, we need to make the loop start at 0:

for(int i = 0; i < array.length; i++){

But even if we fix that error, things can still be wrong. Try compiling and running this:

public class ArrayCounter{

	public static int countArray(int[] array){
		int total = 0;
		for(int i = 0; i < array.length; i++){
			total += array[i];
		}
		return total;
	}
	
	public static void main(String[] args){
		int oneBillion = 1000000000;
		int[] array = {oneBillion, oneBillion, oneBillion};
		int total = countArray(array);
		System.out.println("The total is: " + total);
	}
}

You might expect this to print out 3 billion, but it actually prints out a negative number! What’s going on?

Remember that the different number types (like int and double) hold different types of numbers. The int type holds whole numbers, but they have to be between -2,147,483,648 and 2,147,483,647. This is because the number is stored in a 32-digit binary number, but you don’t really have to understand that part. The point is that when we try to hold 3 billion in an int value, the number goes over the limit and starts back over at the beginning, which is a negative number. That’s why we get a negative number instead of 3 billion.

To fix this error, we could use a long variable, which can hold larger values. For more info on data types in Java, check out the Java documentation.

The lesson is that even if you write code that compiles and runs without any exceptions, it still might contain logic errors. This doesn’t mean you’re a bad programmer! They happen all the time to every single one of us.

Debugging

When you encounter a runtime exception or a logic error, that means it’s time to debug your program. You need to figure out exactly what’s going on in your code, and exactly what in the code is behaving differently from what you expected.

One of the easiest ways to do that is by adding System.out.println() statements to your code. This can help you figure out the values of variables, which functions are being called in what order, how many times a loop is iterating, etc.

Let’s say we have a goal of writing a function that takes a String direction as a parameter, and returns the direction after rotating 90 degrees. We might write this program:

public class DirectionRotater{
	
	public static String rotate(String direction){
		if(direction.equals("up")){
			direction = "right";
		}
		if(direction.equals("right")){
			direction = "down";
		}
		if(direction.equals("down")){
			direction = "left";
		}
		if(direction.equals("left")){
			direction = "up";
		}
		return direction;
	}
	
	public static void main(String[] args){
		String direction = "up";
		direction = rotate(direction);
		direction = rotate(direction);
		System.out.println("Direction: " + direction);
	}
}

This program creates a rotate() function that takes a String parameter. Depending on the value of that parameter, the function reassigns the variable to be a rotated direction: up turns into right, right turns into down, down turns into left, and left turns into up. If this doesn’t make sense, look at the arrow keys on your keyboard, and imagine rotating each key by 90 degrees. Then the function just returns the value.

The main() function then creates a direction variable with a value of "up". The code passes that into the rotate() function, and then reassigns the direction variable to the value returned from that function. It does this twice, so we’d expect the printed direction to be down. But instead, we see that the direction is up!

> java DirectionRotater
Direction: up

(In fact, no matter what direction we pass into the rotate() function, it always returns "up"!)

To figure out what’s going on, we need to read through the program and make sure each line of code is doing what we expect it to. Try reading through the code in your head first, trying to figure out exactly what each line does. Use a piece of paper to jot down variable values.

If you still can’t figure it out, then you should add print statements to your program. Add as many as it takes to figure out exactly what the code is doing.

public class DirectionRotater{
	
	public static String rotate(String direction){
		System.out.println("rotate function got param: " + direction);
		if(direction.equals("up")){
			System.out.println("Entered first if statement.");
			direction = "right";
			System.out.println("Direction is now: " + direction);
		}
		if(direction.equals("right")){
			System.out.println("Entered second if statement.");
			direction = "down";
			System.out.println("Direction is now: " + direction);
		}
		if(direction.equals("down")){
			System.out.println("Entered third if statement.");
			direction = "left";
			System.out.println("Direction is now: " + direction);
		}
		if(direction.equals("left")){
			System.out.println("Entered fourth if statement.");
			direction = "up";
			System.out.println("Direction is now: " + direction);
		}
		
		System.out.println("Returning: " + direction);
		return direction;
	}
	
	public static void main(String[] args){
		String direction = "up";
		System.out.println("Direction starts as: " + direction);
		direction = rotate(direction);
		System.out.println("Rotated once. Direction is now: " + direction);
		direction = rotate(direction);
		System.out.println("Rotated twice. Direction is now: " + direction);
		System.out.println("Direction: " + direction);
	}
}

We’re using System.out.println() to figure out exactly which functions are being called, which if statements are being entered, and the value of variables. Now when we run the code, we see this:

> java DirectionRotater
Direction starts as: up
rotate function got param: up
Entered first if statement.
Direction is now: right
Entered second if statement.
Direction is now: down
Entered third if statement.
Direction is now: left
Entered fourth if statement.
Direction is now: up
Returning: up
Rotated once. Direction is now: up
rotate function got param: up
Entered first if statement.
Direction is now: right
Entered second if statement.
Direction is now: down
Entered third if statement.
Direction is now: left
Entered fourth if statement.
Direction is now: up
Returning: up
Rotated twice. Direction is now: up
Direction: up

Now if we step through the code while reading these print statements, we can see that the rotate() function is getting the correct parameter, but for some reason it’s entering every single if statement instead of just one of them.

This is because we forgot the else part of our if statements! So the rotate() function reaches the first if statement, which it enters because direction starts out as "up". It reassigns direction to be "right”, which is correct so far. But then it looks at the next if statement, which checks whether direction is "right"! It is, so it reassigns it to be "down". This process repeats for all of the if statements, until the last one reassigns direction to be "up".

To fix this, we need to use else if statements:

if(direction.equals("up")){
	direction = "right";
}
else if(direction.equals("right")){
	direction = "down";
}
else if(direction.equals("down")){
	direction = "left";
}
else if(direction.equals("left")){
	direction = "up";
}

Now when one of the if statements is entered, it doesn’t evaluate any of the other if statements. This causes the direction to be rotated only once, which was our original goal!

Let’s look at another example. Let’s say we have a goal of creating a class that represents a line segment. It should hold two Point objects and contain a getLength() function that returns the distance between those points. We might write this code:

import java.awt.Point;

public class LineSegment{

	private Point startPoint;
	private Point endPoint;
	
	public LineSegment(Point startPoint, Point endPoint){
		startPoint = startPoint;
		endPoint = endPoint;
	}
	
	public double getLength(){
		double distance = Math.sqrt(Math.pow(endPoint.getX() - startPoint.getX(), 2) + Math.pow(endPoint.getY() - startPoint.getY(), 2));
		return distance;
	}
	
	public static void main(String[] args){
		Point pointOne = new Point(1, 2);
		Point pointTwo = new Point(100, 200);
		LineSegment line = new LineSegment(pointOne, pointTwo);
		System.out.println("Length: " + line.getLength());
	}
}

This class contains two instances of the Point class (as always, you should check out the Java API whenever you see a class you haven’t seen before) and uses the distance formula to find the length of the line segment.

However, if you run the code you’ll see that the code hits a NullPointerException:

> java LineSegment
Exception in thread "main" java.lang.NullPointerException
        at LineSegment.getLength(LineSegment.java:14)
        at LineSegment.main(LineSegment.java:22)

NullPointerException is probably the most common runtime error, and it means that your code uses a variable that doesn’t have a value. You can debug a NullPointerException exactly like any other error: by stepping through the code and understanding exactly what each line does, and by adding print statements that help you understand exactly what’s happening.

For example, the stack trace tells us that the error is happening on this line:

double distance = Math.sqrt(Math.pow(endPoint.getX() - startPoint.getX(), 2) + Math.pow(endPoint.getY() - startPoint.getY(), 2));
		return distance;

Which means that one of the variables on that line doesn’t have a value. More specifically, the value is null. To figure out exactly which variable is null, we can print out their values just before the line that hits the exception:

public double getLength(){
	System.out.println("startPoint: " + startPoint);
	System.out.println("endPoint: " + endPoint);
	double distance = Math.sqrt(Math.pow(endPoint.getX() - startPoint.getX(), 2) + Math.pow(endPoint.getY() - startPoint.getY(), 2));
	return distance;
}

When we run that code, we can see that both startPoint and endPoint are null!

> java LineSegment

startPoint: null
endPoint: null

Exception in thread "main" java.lang.NullPointerException
        at LineSegment.getLength(LineSegment.java:16)
        at LineSegment.main(LineSegment.java:24)

So now we have to work backwards and find out why those values are null. We could add more print statements:

import java.awt.Point;

public class LineSegment{

	private Point startPoint;
	private Point endPoint;
	
	public LineSegment(Point startPoint, Point endPoint){
		System.out.println("in constructor, parameter startPoint: " + startPoint);
		System.out.println("in constructor, parameter endPoint: " + endPoint);
		
		startPoint = startPoint;
		endPoint = endPoint;
		
		System.out.println("in constructor, this.startPoint: " + this.startPoint);
		System.out.println("in constructor, this.endPoint: " + this.endPoint);
	}
	
	public double getLength(){
		System.out.println("startPoint: " + startPoint);
		System.out.println("endPoint: " + endPoint);
		double distance = Math.sqrt(Math.pow(endPoint.getX() - startPoint.getX(), 2) + Math.pow(endPoint.getY() - startPoint.getY(), 2));
		return distance;
	}
	
	public static void main(String[] args){
		Point pointOne = new Point(1, 2);
		Point pointTwo = new Point(100, 200);
		
		System.out.println("in main, pointOne: " + pointOne);
		System.out.println("in main, pointTwo: " + pointTwo);
		
		LineSegment line = new LineSegment(pointOne, pointTwo);
		System.out.println("Length: " + line.getLength());
	}
}

Then we can run that code to get the output:

> java LineSegment
in main, pointOne: java.awt.Point[x=1,y=2]
in main, pointTwo: java.awt.Point[x=100,y=200]
in constructor, parameter startPoint: java.awt.Point[x=1,y=2]
in constructor, parameter endPoint: java.awt.Point[x=100,y=200]
in constructor, this.startPoint: null
in constructor, this.endPoint: null
startPoint: null
endPoint: null

Exception in thread "main" java.lang.NullPointerException
        at LineSegment.getLength(LineSegment.java:22)
        at LineSegment.main(LineSegment.java:34)

Now you can use the output to help you trace through the code in your head. You can see that the points have values inside the main() function, and the parameters are passed correctly. But then the class-level variables aren’t set, which is why they’re null when we call the getLength() function. So something is wrong with how we’re setting the class-level variables using the parameter variables:

startPoint = startPoint;
endPoint = endPoint;

Now that you have the problem narrowed down to just a couple lines, you can go back and read the tutorials for what those lines are doing. In this case, we’re creating a class. You might read through that and realize that we needed to use the this keyword to reference the class-level variables! Without that, these lines are just assigning the parameters back to themselves, which doesn’t change anything. We needed to do this:

this.startPoint = startPoint;
this.endPoint = endPoint;

Now our code sets the class-level variables to the values of the parameters. Now our code works fine!

> java LineSegment
Length: 221.37072977247917

If you still can’t figure it out after debugging, adding print statements, and narrowing it down to just a few lines that aren’t behaving correctly, then you can get help by posting in the forum!

General Tips

Debugging can be frustrating, but it’s a huge part of being a programmer. Here are a couple tips to keep yourself sane:

  • Blame yourself first. It can be tempting to assume you found a bug in Java, or in the library you’re using. And while it’s not impossible that you found a bug, it’s much more likely that it’s a bug in your code. So you should ask yourself “what am I doing wrong?” and “which of my assumptions is incorrect?” instead of blaming the code.
  • Don’t take it personally. Those questions don’t mean that you’re a bad programmer. Bugs happen to every single programmer, every single time they sit down to write code. So try not to get frustrated, especially when people ask you to narrow your problem down or debug your program. Just go through the process we just talked about: run through your code in your head, and use print statements to figure out what’s going on.
  • Work in small chunks. Don’t try to write your program all at one time. You should be recompiling as often as possible- after every line if you can. Test single methods by themselves with hard-coded values, and make sure it’s doing exactly what you expected before you move on to the next step. That will also make it easier to get help when you do get stuck!

If all else fails, sometimes the best thing you can do is take a break. Go on a walk, pet your cat, and try to clear your head. You’ll be amazed at how many solutions you end up thinking of in the shower.

Homework

  • Write a program that outputs 100 random points to a text file. Write another program that reads in those points and prints out the center of all those points.

As developers, we would like our users to interact with applications that run smoothly and without issues. We want the libraries that we create to be widely adopted and successful. All of that will not happen without the code that handles errors.

Java exception handling is often a significant part of the application code. You might use conditionals to handle cases where you expect a certain state and want to avoid erroneous execution – for example, division by zero. However, there are cases where conditions are not known or are well hidden because of third party code. In other cases, code is designed to create an exception and throw it up the stack to allow for the graceful handling of a Java error at a higher level.

If all this is new to you, this tutorial will help you understand what Java exceptions are, how to handle them and the best practices you should follow to maintain the natural flow of the application.

What Is an Exception in Java?

An Exception is an event that causes your normal Java application flow to be disrupted. Exceptions can be caused by using a reference that is not yet initialized, dividing by zero, going beyond the length of an array, or even JVM not being able to assign objects on the heap.

In Java, an exception is an Object that wraps the error that caused it and includes information like:

  • The type of error that caused the exception with the information about it.
  • The stack trace of the method calls.
  • Additional custom information that is related to the error.

Various Java libraries throw exceptions when they hit a state of execution that shouldn’t happen – from the standard Java SDK to the enormous amounts of open source code that is available as a third-party library.

Exceptions can be created automatically by the JVM that is running our code or explicitly our code itself. We can extend the classes that are responsible for exceptions and create our own, specific Java exceptions that handle unexpected situations. But keep in mind that throwing an exception doesn’t come for free. It is expensive. The larger the call stack the more expensive the exception.

Exception handling in Java is a mechanism to process, catch and throw Java errors that signal an abnormal state of the program so that the execution of the business code can continue uninterrupted.

Why Handle Java Exceptions?

Using Java exceptions is very handy; they let you separate the business logic from the application code for handling errors. However, exceptions are neither free nor cheap. Throwing an exception requires the JVM to perform operations like gathering the full stack trace of the method calls and passing it to the method that is responsible for handling the exception. In other words, it requires additional resources compared to a simple conditional.

No matter how expensive the exceptions are, they are invaluable for troubleshooting issues. Correlating exceptions with other data like JVM metrics and various application logs can help with resolving your problems fast and in a very efficient manner.

Exception Class Hierarchy

Java is an object-oriented language, and thus, every class will extend the java.lang.Object. The same goes for the Throwable class, which is the base class for the errors and exceptions in Java. The following picture illustrates the class hierarchy of the Throwable class and its most common subclasses:

The Difference Between a Java Exception and Error

Before going into details about the Exception class, we should ask one fundamental question: What is the difference between an Error and an Exception in Java?

To answer the question, let’s look at Javadocs and start from the Throwable class. The Throwable class is the mother of all errors–the superclass of all errors and exceptions that your Java code can produce. Only objects that are instances of the Throwable class or any of its subclasses can be thrown by the code running inside the JVM or can be declared in the methods throw clause. There are two main implementations of the Throwable class.

A Java Error is a subclass of Throwable that represents a serious problem that a reasonable application should not try to catch. The method does not have to declare an Error or any of its subclasses in its throws clause for it to be thrown during the execution of a method. The most common subclasses of the Error class are Java OutOfMemoryError and StackOverflowError classes. The first one represents JVM not being able to create a new object; we discussed potential causes in our article about the JVM garbage collector. The second error represents an issue when the application recurses too deeply.

The Java Exception and its subclasses, on the other hand, represent situations that an application should catch and try to handle. There are lots of implementations of the Exception class that represent situations that your application can gracefully handle. The FileNotFoundException, SocketException, or NullPointerException are just a few examples that you’ve probably come across when writing Java applications.

Types of Java Exceptions

There are multiple implementations of the Exception class in Java. They come from the Java Development Kit itself, but also from various libraries and applications that you might be using when writing your own code. We will not discuss every Exception subclass that you can encounter, but there are two main types that you should be aware of to understand how exception handling works in Java–the checked and unchecked, aka runtime exceptions.

Checked Exceptions

The Java checked exceptions are the ones that implement the Exception class and not the RuntimeException. They are called checked exceptions because the compiler verifies them during the compile-time and refuses to compile the code if such exceptions are not handled or declared. Instead, the compiler will notify you to handle these exceptions. For example, the FileNotFoundException is an example of such an exception.

In order for a method or constructor to throw a checked exception it needs to be explicitly declared:

public CheckedExceptions() throws FileNotFoundException {
  throw new FileNotFoundException("File not found");
}

public void throwsExample() throws FileNotFoundException {
  throw new FileNotFoundException("File not found");
}

You can see that in both cases we explicitly have the throws clause mentioning the FileNotFoundException that can be thrown by the execution of the constructor of the throwsExample method. Such code will be successfully compiled and can be executed.

On the other hand, the following code will not compile:

public void wrongThrowsExample() {
  throw new FileNotFoundException("File not found");
}

The reason for the compiler not wanting to compile the above code will be raising the checked exception and not processing it. We need to either include the throws clause or handle the Java exception in the try/catch/finally block. If we were to try to compile the above code as is the following error would be returned by the compiler:

/src/main/java/com/sematext/blog/java/CheckedExceptions.java:18: error: unreported exception FileNotFoundException; must be caught or declared to be thrown
    throw new FileNotFoundException("File not found");
    ^

Unchecked Exceptions / Runtime Exceptions

The unchecked exceptions are exceptions that the Java compiler does not require us to handle. The unchecked exceptions in Java are the ones that implement the RuntimeException class. Those exceptions can be thrown during the normal operation of the Java Virtual Machine. Unchecked exceptions do not need to be declared in the method throws clause in order to be thrown. For example, this block of code will compile and run without issues:

public class UncheckedExceptions {
  public static void main(String[] args) {
    UncheckedExceptions ue = new UncheckedExceptions();
    ue.run();
  }

  public void run() {
    throwRuntimeException();
  }

  public void throwRuntimeException() {
    throw new NullPointerException("Null pointer");
  }
}

We create an UncheckedExceptions class instance and run the run method. The run method calls the throwRuntimeException method which creates a new NullPointerException. Because the NullPointerException is a subclass of the RuntimeException we don’t need to declare it in the throws clause.

The code compiles and propagates the exception up to the main method. We can see that in the output when the code is run:

Exception in thread "main" java.lang.NullPointerException: Null pointer
        at com.sematext.blog.java.UncheckedExceptions.throwRuntimeException(UncheckedExceptions.java:14)
        at com.sematext.blog.java.UncheckedExceptions.run(UncheckedExceptions.java:10)
        at com.sematext.blog.java.UncheckedExceptions.main(UncheckedExceptions.java:6)

This is exactly what we expected.

The most common subclasses of the Java RuntimeException that you probably saw already are ClassCastException, ConcurrentModificationException, IllegalArgumentException, or everyone’s favorite, NullPointerException.

Let’s now look at how we throw exceptions in Java.

How to Handle Exceptions in Java: Code Examples

Handling exceptions in Java is a game of using five keywords that combined give us the possibility of handling errors – the try, catch, finally, throw, and throws.

The first one – try is used to specify the block of code that can throw an exception:

try {
   File file = openNewFileThatMayNotExists(location);
   // process the file
}

The try block must be followed by the catch or finally blocks. The first one mentioned can be used to catch and process exceptions thrown in the try block:

try {
   ...
} catch (IOException ioe) {
   // handle missing file
}

The finally block can be used to handle the code that needs to be executed regardless of whether the exception happened or not.

The throw keyword is used to create a new Exception instance and the throws keyword is used to declare what kind of exceptions can be expected when executing a method.

When handling exceptions in Java, we don’t want to just throw the created exception to the top of the call stack, for example, to the main method. That would mean that each and every exception that is thrown would crash the application and this is not what should happen. Instead, we want to handle Java exceptions, at least the ones that we can deal with, and either help fixing the problem or fail gracefully.

Java gives us several ways to handle exceptions.

Throwing Exceptions

An Exception in Java can be handled by using the throw keyword and creating a new Exception or re-throwing an already created exception. For example, the following very naive and simple method creates an instance of the File class and checks if the file exists. If the file doesn’t exist the method throws a new IOException:

public File openFile(String path) throws IOException {
  File file = new File(path);
  if (!file.exists()) {
    throw new IOException(String.format("File %s doesn't exist", path));
  }
  // continue execution of the business logic
  return file;
}

You can also re-throw a Java exception that was thrown inside the method you are executing. This can be done by the try-catch block:

public class RethrowException {
  public static void main(String[] args) throws IOException {
    RethrowException exec = new RethrowException();
    exec.run();
  }

  public void run() throws IOException {
    try {
     methodThrowingIOE();
    } catch (IOException ioe) {
      // do something about the exception
      throw ioe;
    }
  }

  public void methodThrowingIOE() throws IOException {
    throw new IOException();
  }
}

You can see that the run method re-throws the IOException that is created in the methodThrowingIOE. If you plan on doing some processing of the exception, you can catch it as in the above example and throw it further. However, keep in mind that in most cases, this is not a good practice. You shouldn’t catch the exception, process it, and push it up the execution stack. If you can’t process it, it is usually better to pack it into a more specialized Exception class, so that a dedicated error processing code can take care of it. You can also just decide that you can’t process it and just throw it:

public class RethrowException {
  public static void main(String[] args) throws IOException {
    RethrowException exec = new RethrowException();
    exec.run();
  }

  public void run() throws IOException {
    try {
     methodThrowingIOE();
    } catch (IOException ioe) {
      throw ioe;
    }
  }

  public void methodThrowingIOE() throws IOException {
    throw new IOException();
  }
}

There are additional cases, but we will discuss them when talking about how to catch exceptions in Java.

Try-Catch Block

The simplest and most basic way to handle exceptions is to use the try – catch block. The code that can throw an exception is put into the try block and the code that should handle it is in the catch block. The exception can be either checked or unchecked. In the first case, the compiler will tell you which kind of exceptions need to be caught or defined in the method throws clause for the code to compile. In the case of unchecked exceptions, we are not obligated to catch them, but it may be a good idea – at least in some cases. For example, there is a DOMException or DateTimeException that indicate issues that you can gracefully handle.

To handle the exception, the code that might generate an exception should be placed in the try block which should be followed by the catch block. For example, let’s look at the code of the following openFileReader method:

public Reader openFileReader(String filePath) {
  try {
    return new FileReader(filePath);
  } catch (FileNotFoundException ffe) {
    // tell the user that the file was not found
  }
  return null;
}

The method tries to create a FileReader class instance. The constructor of that class can throw a checked FileNotFoundException if the file provided as the argument doesn’t exist. We could just include the FileNotFoundException in the throws clause of our openFileReader method or catch it in the catch section just like we did. In the catch section, we can do whatever we need to get a new file location, create a new file, and so on.

If we would like to just throw it further up the call stack the code could be further simplified:

public Reader openFileReaderWithThrows(String filePath) throws FileNotFoundException {
  return new FileReader(filePath);
}

This passes the responsibility of handling the FIleNotFoundException to the code that calls it.

Multiple Catch Block

We are not limited to a single catch block in the try-catch block. We can have multiple try-catch blocks that allow you to handle each exception differently, would you need that. Let’s say that we have a method that lists more than a single Java exception in its throws clause, like this:

public void readAndParse(String file) throws FileNotFoundException, ParseException {
  // some business code
}

In order to compile and run this we need multiple catch blocks to handle each of the listed Java exceptions:

public void run(String file) {
  try {
    readAndParse(file);
  } catch (FileNotFoundException ex) {
    // do something when file is not found
  } catch (ParseException ex) {
    // do something if the parsing fails
  }
}

Such code organization can be used when dealing with multiple Java exceptions that need to be handled differently. Hypothetically, the above code could ask for a new file location when the FileNotFoundException happens and inform the user of the issues with parsing when the ParseException happens.

There is one more thing we should mention when it comes to handle exceptons using multiple catch blocks. You need to remember that the order of the catch blocks matters. If you have a more specialized Java exception in the catch block after the more general exception the more specialized catch block will never be executed. Let’s look at the following example:

public void runTwo(String file) {
  try {
    readAndParse(file);
  } catch (Exception ex) {
    // this block will catch all exceptions
  } catch (ParseException ex) {
    // this block will not be executed
  }
}

Because the first catch block catches the Java Exception the catch block with the ParseException will never be executed. That’s because the ParseException is a subclass of the Exception.

Catching Multiple Exceptions

To handle multiple Java exceptions with the same logic, you can list them all inside a single catch block. Let’s redo the above example with the FileNotFoundException and the ParseException to use a single catch block:

public void runSingleCatch(String file) {
  try {
    readAndParse(file);
  } catch (FileNotFoundException | ParseException ex) {
    // do something when file is not found
  }
}

You can see how easy it is–you can connect multiple Java exceptions together using the | character. If any of these exceptions is thrown in the try block the execution will continue inside the catch block.

The Finally Block

In addition to try and the catch blocks, there is one additional section that may come in handy when handling exceptions in Java–the finally block. The finally block is the last section in your try-catch-finally expression and will always get executed–either after the try or after the catch. It will be executed even if you use the return statement in the try block, which by the way, you shouldn’t do.

Let’s look at the following example:

public void exampleOne() {
  FileReader reader = null;
  try {
    reader = new FileReader("/tmp/somefile");
    // do some processing
  } catch (FileNotFoundException ex) {
    // do something
  } finally {
    if (reader != null) {
      try {
        reader.close();
      } catch (IOException ex) {
        // do something
      }
    }
  }
}

You can see the finally block. Keep in mind that you can have at most one finally block present. The way this code will be executed is as follows:

  1. The JVM will execute the try section first and will try to create the FileReader instance.
  2. If the /tmp/somefile is present and readable the try block execution will continue.
  3. If the /tmp/somefile is not present or is not readable the FileNotFoundException will be raised and the execution of the code will go to the catch block.
  4. After 2) or 3) the finally block will be executed and will try to close the FileReader instance if it is present.

The finally section will be executed even if we modify the try block and include the return statement. The following code does exactly the same as the one above despite having the return statement at the end of the try block:

public void exampleOne() {
  FileReader reader = null;
  try {
    reader = new FileReader("/tmp/somefile");
    // do something
    return;
  } catch (FileNotFoundException ex) {
    // do something
  } finally {
    if (reader != null) {
      try {
        reader.close();
      } catch (IOException ex) {
        // do something
      }
    }
  }
}

So, why and when should you use the finally block to handle exceptions in Java? Well, it is a perfect candidate for cleaning up resources, like closing objects, calculating metrics, including Java logs about operation completion, and so on.

The Try-With-Resources Block

The last thing when it comes to handling exceptions in Java is the try-with-resources block. The idea behind that Java language structure is opening resources in the try section that will be automatically closed at the end of the statement. That means that instead of including the finally block we can just open the resources that we need in the try section and rely on the Java Virtual Machine to deal with the closing of the resource.

For the class to be usable in the try-with-resource block it needs to implement the java.lang.AutoCloseable, which includes every class implementing the java.io.Closeable interface. Keep that in mind.

An example method that reads a file using the FileReader class which uses the try-with-resources might look as follows:

public void readFile(String filePath) {
  try (FileReader reader = new FileReader(filePath)) {
    // do something
  } catch (FileNotFoundException ex) {
    // do something when file is not found
  } catch (IOException ex) {
    // do something when issues during reader close happens
  }
}

Of course, we are not limited to a single resource and we can have multiple of them, for example:

public void readFiles(String filePathOne, String filePathTwo) {
  try (
      FileReader readerOne = new FileReader(filePathOne);
      FileReader readerTwo = new FileReader(filePathTwo);
  ) {
    // do something
  } catch (FileNotFoundException ex) {
    // do something when file is not found
  } catch (IOException ex) {
    // do something when issues during reader close happens
  }
}

One thing that you have to remember is that you need to process the Java exceptions that happen during resources closing in the catch section of the try-catch-finally block. That’s why in the above examples, we’ve included the IOException in addition to the FileNotFoundException. The IOException may be thrown during FileReader closing and we need to process it.

Catching User-Defined Exceptions

The Throwable and Exception are Java classes and so you can easily extend them and create your own exceptions. Depending on if you need a checked or unchecked exception you can either extend the Exception or the RuntimeException class. To give you a very simple example on how such code might look like, have a look at the following fragment:

public class OwnExceptionExample {
  public void doSomething() throws MyOwnException {
    // code with very important logic
    throw new MyOwnException("My Own Exception!");
  }

  class MyOwnException extends Exception {
    public MyOwnException(String message) {
      super(message);
    }
  }
}

In the above trivial example we have a new Java Exception implementation called MyOwnException with a constructor that takes a single argument – a message. It is an internal class, but usually you would just move it to an appropriate package and start using it in your application code.

How to Catch Specific Java Exceptions

Catching specific Java exceptions is not very different from handling a general exception. Let’s look at the code examples. Catching exceptions that implement the Exception class is as simple as having a code similar to the following one:

try {
  // code that can result in exception throwing
} catch (Exception ex) {
  // handle exception
}

The catch block in the above example would run for every object implementing the Exception class. These include IOException, EOFException, FileNotFoundException, or the NullPointerException. But sometimes we may want to have different behavior depending on the exception that was thrown. You may want different behavior for the IOException and different for the FileNotFoundException. This can be achieved by having multiple catch blocks:

try {
  // code that can result in exception throwing
} catch (FileNotFoundException ex) {
  // handle exception
} catch (IOException ex) {
  // handle exception
}

There is one thing to remember though. The first catch clause that will match the exception class will be executed. The FileNotFoundException is a subclass of the IOException. That’s why I specified the FileNotFoundException as the first one. If I would do the opposite, the block with the IOException handling would catch all IOException instances and their subclasses. Something worth remembering.

Finally, we’ve mentioned that before, but I wanted to repeat myself here – if you would like to handle numerous exceptions with the same code you can do that by combining them in a single catch block:

try {
  // code that can result in exception throwing
} catch (FileNotFoundException | EOFException ex) {
  // handle exception
} catch (IOException ex) {
  // handle exception
}

That way you don’t have to duplicate the code.

Java Exception Handling Best Practices

There are a lot of best practices when it comes to handling exceptions in the Java Virtual Machine world, but I find a few of them useful and important.

Keep Exceptions Use to a Minimum

Every time you throw an Exception it needs to be handled by the JVM. Throwing an exception doesn’t cost much, but for example, getting the stack trace for the exception is noticeable, especially when you deal with a large number of concurrent requests and get the stack trace for every exception.

What I will say is not true for every situation and for sure makes the code a bit uglier, but if you want to squeeze every bit of performance from your code, try to avoid Java exceptions when a simple comparison is enough. Look at the following method:

public int divide(int dividend, int divisor) {
  try {
    return dividend / divisor;
  } catch (ArithmeticException ex) {
    LOG.error("Error while division, stack trace:", ex.getStackTrace());
  }
  return -1;
}

It tries to divide two numbers and catches the ArithmeticException in case of a Java error. What kind of error can happen here? Well, division by zero is the perfect example. If the divisor argument is 0 the code will throw an exception, catch it to avoid failure in the code and continue returning -1. This is not the perfect way to go since throwing a Java exception has a performance penalty – we talk about it later in the blog post. In such cases, you should check the divisor argument and allow the division only if it is not equal to 0, just like this:

public int divide(int dividend, int divisor) {
  if (divisor != 0) {
    return 10 / divisor;
  }
  return -1;
}

The performance of throwing an exception is not high, but parts of the error handling code may be expensive – for example stack trace retrieval. Keep that in mind when writing your code that handles Java exceptions and situations that should not be allowed.

Always Handle Exceptions, Don’t Ignore Them

Always handle the Java Exception, unless you don’t care about one. But if you think that you should ignore one, think twice. The methods that you call throw exceptions for a reason and you may want to process them to avoid problematic situations.

Process the exceptions, log them, or just print them to the console. Avoid doing things you can see in the following code:

try {
  FileReader reader = new FileReader(filePath);
  // some business code
} catch (FileNotFoundException ex) {}

As you can see in the code above the FileNotFoundException is hidden and we don’t have any idea that the creation of the FileReader failed. Of course, the code that runs after the creation of the FileReader would probably fail if it were to operate on that. Now imagine that you are catching Java exceptions like this:

try {
  FileReader reader = new FileReader(filePath);
  // some business code
} catch (Exception ex) {}

You would catch lots of Java exceptions and completely ignore them all. The least you would like to do is fail and log the information so that you can easily find the problem when doing log analysis or setting up an alert in your log centralization solution. Of course, you may want to process the exception, maybe do some interaction with external systems or the user itself, but for sure not hide the problem.

If you really don’t want to handle an exception in Java you can just print it to the log or error stream, for example:

try {
  FileReader reader = new FileReader(filePath);
  // some business code
} catch (Exception ex) {
  ex.printStackTrace();
}

Or even better, if you are using a centralized logging solution just do the following to store the error log there:

try {
  FileReader reader = new FileReader(filePath);
  // some business code
} catch (Exception ex) {
  LOG.error("Error during FileReader creation", ex);
}

Use Descriptive Messages When Throwing Exceptions

When using exceptions, think about the person who will be looking at the logs or will be troubleshooting your application. Think about what kind of information will be needed to quickly and efficiently find the root cause of the problem – Java exceptions in our case.

When throwing an exception you can use code like this:

throw new FileNotFoundException(“file not found”);

Or code that looks like this:

throw new FileNotFoundException(String.format(“File %s not found in directory %s”, file, directory));

The first one will just say that some mystery file was not found. The person that will try to diagnose and fix the problem will not know which and where the file is expected to be. The second example explicitly mentions which file is expected and where – and it is exactly what you should do when throwing exceptions.

Never Use Return Statement in the Finally Block

The finally block is the place in which you can execute code regardless if the exception happened or not – for example close the opened resources to avoid leaks. The next thing you should avoid is using the return statement in the finally block of your code. Have a look at the following code fragment:

public class ReturnInFinally {
  public static void main(String[] args) {
    ReturnInFinally app = new ReturnInFinally();
    app.example();
    System.out.println("Ended without error");
  }

  public void example() {
    try {
      throw new NullPointerException();
    } finally {
      return;
    }
  }
}

If you were to run it, it would end up printing the Ended without error to the console. But we did throw the NullPointerException, didn’t we? Yes, but that would be hidden because of our finally block. We didn’t catch the exception and according to Java language Exception handling specification, the exception would just be discarded. We don’t want something like that to happen, because that would mean that we are hiding issues in the code and its execution.

Never Use Throw Statement in the Finally Block

A very similar situation to the one above is when you try to throw a new Java Exception in the finally block. Again, the code speaks louder than a thousand words, so let’s have a look at the following example:

public class ThrowInFinally {
  public static void main(String[] args) {
    ThrowInFinally app = new ThrowInFinally();
    app.example();
  }

  public void example() {
    try {
      throw new RuntimeException("Exception in try");
    } finally {
      throw new RuntimeException("Exception in finally");
    }
  }
}

If you were to execute the above code the sentence that will be printed in the error console would be the following:

Exception in thread "main" java.lang.RuntimeException: Exception in finally
  at com.sematext.blog.java.ThrowInFinally.example(ThrowInFinally.java:13)
  at com.sematext.blog.java.ThrowInFinally.main(ThrowInFinally.java:6)

Yes, it is true. The Java RuntimeException that was thrown in the try block will be hidden and the one that was thrown in the finally block will take its place. It may not seem big, but have a look at the code like this:

public void example() throws Exception {
  FileReader reader = null;
  try {
    reader = new FileReader("/tmp/somefile");
  } finally {
    reader.close();
  }
}

Now think about the execution of the code and what happens in a case where the file specified by the filePath is not available. First, the FileReader constructor would throw a FileNotFoundException and the execution would jump to the finally block. Once it gets there it would try to call the close method on a reference that is null. That means that the NullPointerException would be thrown here and the whole example method would throw it. So practically, the FileNotFoundException would be hidden and thus the real problem would not be easily visible that well.

Performance Side Effects of Using and Handling Exceptions in Java

We’ve mentioned that using exceptions in Java doesn’t come for free. Throwing an exception requires the JVM to fill up the whole call trace, list each method that comes with it, and pass it further to the code that will finally catch and handle the Java exception. That’s why you shouldn’t use exceptions unless it is really necessary.

Let’s look at a very simple test. We will run two classes–one will do the division by zero and catch the exception that is caused by that operation. The other code will check if the divisor is zero before throwing the exception. The mentioned code is encapsulated in a method.

The code that uses an exception looks as follows:

public int divide(int dividend, int divisor) {
  try {
    return dividend / divisor;
  } catch (Exception ex) {
    // handle exception
  }
  return -1;
}

The code that does a simple check using a conditional looks as follows:

public int divide(int dividend, int divisor) {
  if (divisor != 0) {
    return 10 / divisor;
  }
  return -1;
}

One million executions of the first method take 15 milliseconds, while one million executions of the second method take 5 milliseconds. So you can clearly see that there is a large difference in the execution time when running the code with exceptions and when using a simple conditional to check if the execution should be allowed. Of course, keep in mind that this comparison is very, very simple, and doesn’t reflect real-world scenarios, but it should give you an idea of what to expect when crafting your own code.

The test execution time will differ depending on the machine, but you can run it on your own if you would like by cloning this Github repository.

Troubleshooting Java Exceptions with Sematext

Handling exceptions properly in your Java code is important. Equally important is being able to use them for troubleshooting your Java applications. With Sematext Cloud you can find the most frequent errors and exceptions in your Java application logs, create custom dashboards for your team and set up real-time alerts and anomaly detection to notify you of issues that require your attention. Can you also correlate errors, exceptions, and other application logs with your application performance metrics? You can use Sematext not only for Java monitoring, but also to monitor Elasticsearch, Tomcat, Solr, Kafka, Nginx, your servers, processes, databases, even your packages, and the rest of your infrastructure. With service auto-discovery and monitoring everything is just a click away 🙂

Conclusion

Even though exceptions in Java are not free and have a performance penalty they are invaluable for handling errors in your applications. They help in decoupling the main business code from the error handling code making the whole code cleaner and easier to understand. Using exceptions wisely will make your code look good, be extensible, maintainable, and fun to work with. Use them following exception handling best practices and log everything they tell you into your centralized logging solution so that you can get the most value out of any thrown exception.

If you don’t know where to start looking for the perfect logging solution for you, we wrote in-depth comparisons of various log management tools, log analyzers, and cloud logging services available today to fund the one that fits your use case.

Понравилась статья? Поделить с друзьями:
  • Java exe not found error
  • Java exception has occurred как исправить майнкрафт
  • Java exception has occurred как исправить windows 10
  • Java exception has occurred как исправить ubnt
  • Java exception error minecraft