Skip to content

Throw Exception

abnormal spread

When a method throws an exception, if the current method does not catch the exception, the exception will be thrown to the upper calling method until it encounters a try ... catch is caught:

java
// exception
public class Main {
  public static void main(String[] args) {
    try {
      process1();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  static void process1() {
    process2();
  }

  static void process2() {
    Integer.parseInt(null); // Will throw NumberFormatException
  }
}

The call stack of the method can be printed out through printStackTrace() , similar to:

sh
java.lang.NumberFormatException: null
    at java.base/java.lang.Integer.parseInt(Integer.java:614)
    at java.base/java.lang.Integer.parseInt(Integer.java:770)
    at Main.process2(Main.java:16)
    at Main.process1(Main.java:12)
    at Main.main(Main.java:5)

printStackTrace() is very useful for debugging errors. The above information indicates NumberFormatException is thrown in the java.lang.Integer.parseInt method. Looking from bottom to top, the calling hierarchy is:

  • main() calls process1() ;
  • process1() calls process2() ;
  • process2() calls Integer.parseInt(String) ;
  • Integer.parseInt(String) calls Integer.parseInt(String, int) .

Looking at the Integer.java source code, we can see that the method code for throwing an exception is as follows:

java
public static int parseInt(String s, int radix) throws NumberFormatException {
    if (s == null) {
        throw new NumberFormatException("null");
    }
    ...
}

Moreover, the line number of the source code is given for each layer of call, which can be directly located.

throw an exception

When an error occurs, for example, the user enters an illegal character, we can throw an exception.

How to throw an exception? Referring to Integer.parseInt() method, there are two steps to throwing an exception:

  • Create an instance of Exception ;
  • Throw using throw statement.

Here's an example:

java
void process2(String s) {
    if (s==null) {
        NullPointerException e = new NullPointerException();
        throw e;
    }
}

In fact, most of the code that throws exceptions will be combined into one line:

java
void process2(String s) {
    if (s==null) {
        throw new NullPointerException();
    }
}

If a method catches an exception and then throws a new exception in the catch clause, it is equivalent to "converting" the thrown exception type:

java
void process1(String s) {
    try {
        process2();
    } catch (NullPointerException e) {
        throw new IllegalArgumentException();
    }
}

void process2(String s) {
    if (s==null) {
        throw new NullPointerException();
    }
}

When process2() throws NullPointerException , it is caught by process1() , and then throws IllegalArgumentException() .

If IllegalArgumentException is caught in main() , let's look at the printed exception stack:

java
public class Main {
    public static void main(String[] args) {
        try {
            process1();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    static void process1() {
        try {
            process2();
        } catch (NullPointerException e) {
            throw new IllegalArgumentException();
        }
    }

    static void process2() {
        throw new NullPointerException();
    }
}

The printed exception stack is similar to:

sh
java.lang.IllegalArgumentException
    at Main.process1(Main.java:15)
    at Main.main(Main.java:5)

This shows that the new exception has lost the original exception information, and we can no longer see the information of the original exception NullPointerException .

In order to track the complete exception stack, when constructing the exception, pass the original Exception instance in, and the new Exception can hold the original Exception information. Improvements to the above code are as follows:

java
public class Main {
    public static void main(String[] args) {
        try {
            process1();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    static void process1() {
        try {
            process2();
        } catch (NullPointerException e) {
            throw new IllegalArgumentException(e);
        }
    }

    static void process2() {
        throw new NullPointerException();
    }
}

Running the above code, the printed exception stack is similar to:

sh
java.lang.IllegalArgumentException: java.lang.NullPointerException
    at Main.process1(Main.java:15)
    at Main.main(Main.java:5)
Caused by: java.lang.NullPointerException
    at Main.process2(Main.java:20)
    at Main.process1(Main.java:13)

NullPointerException IllegalArgumentException Caused Main.process2() Caused by: Xxx

To get the original exception in code, use the Throwable.getCause() method. If null is returned, it means that it is already a "root exception".

With complete exception stack information, we can quickly locate and fix code problems.

Best practices

When an exception is caught and thrown again, the original exception must be retained, otherwise it will be difficult to locate the first crime scene!

If we throw an exception in the try or catch statement block, will the finally statement be executed? For example:

java
public class Main {
    public static void main(String[] args) {
        try {
            Integer.parseInt("abc");
        } catch (Exception e) {
            System.out.println("catched");
            throw new RuntimeException(e);
        } finally {
            System.out.println("finally");
        }
    }
}

The results of executing the above code are as follows:

sh
catched
finally
Exception in thread "main" java.lang.RuntimeException: java.lang.NumberFormatException: For input string: "abc"
    at Main.main(Main.java:8)
Caused by: java.lang.NumberFormatException: For input string: "abc"
    at ...

The first line prints catched , indicating that the catch statement block has been entered. The second line prints finally , indicating that the finally statement block is executed.

Therefore, throwing an exception in catch will not affect the execution of finally . The JVM will execute finally first and then throw an exception.

Exception shielding

If an exception is thrown when executing the finally statement, can the exception in the catch statement continue to be thrown? For example:

java
public class Main {
    public static void main(String[] args) {
        try {
            Integer.parseInt("abc");
        } catch (Exception e) {
            System.out.println("catched");
            throw new RuntimeException(e);
        } finally {
            System.out.println("finally");
            throw new IllegalArgumentException();
        }
    }
}

Executing the above code, the exception information is found as follows:

sh
catched
finally
Exception in thread "main" java.lang.IllegalArgumentException
    at Main.main(Main.java:11)

This shows that after finally throwing an exception, the exception originally prepared to be thrown in catch "disappears" because only one exception can be thrown. An exception that is not thrown is called a "Suppressed Exception".

In rare cases, we need to know all exceptions. How to save all exception information? The method is to first use origin variable to save the original exception, then call Throwable.addSuppressed() to add the original exception, and finally throw it:

java
public class Main {
    public static void main(String[] args) throws Exception {
        Exception origin = null;
        try {
            System.out.println(Integer.parseInt("abc"));
        } catch (Exception e) {
            origin = e;
            throw e;
        } finally {
            Exception e = new IllegalArgumentException();
            if (origin != null) {
                e.addSuppressed(origin);
            }
            throw e;
        }
    }
}

When both catch and finally throw exceptions, although catch exception is blocked, the exception thrown by finally still contains it:

sh
Exception in thread "main" java.lang.IllegalArgumentException
    at Main.main(Main.java:11)
Suppressed: java.lang.NumberFormatException: For input string: "abc"
    at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    at java.base/java.lang.Integer.parseInt(Integer.java:652)
    at java.base/java.lang.Integer.parseInt(Integer.java:770)
    at Main.main(Main.java:6)

All Suppressed Exception can be obtained through Throwable.getSuppressed() .

In most cases, do not throw exceptions in finally . Therefore, we usually don't need to care about Suppressed Exception .

Post an exception when asking a question

The detailed stack information printed by the exception is the key to finding the problem. Many beginners only post the code and not the exception when asking questions, which is equivalent to only reporting the crime without giving clues, and there is nothing Holmes can do.

Some children's shoes only post part of the abnormal information, and the most critical Caused by: xxx is omitted. This is an incorrect way of asking questions and must be changed.

Summary

Calling printStackTrace() can print the exception propagation stack, which is very useful for debugging;

When catching an exception and throwing a new exception again, the original exception information should be kept;

Generally don't throw exceptions in finally . If an exception is thrown in finally , the original exception should be added to the original exception. The caller can obtain all added Suppressed Exception through Throwable.getSuppressed() .

Throw Exception has loaded