Rethrowing an Exception: Why Does the Method Compile Without a Throws Clause

Rethrowing an Exception: Why does the method compile without a throws clause?

This behavior appears to occur only on Java 1.7. When compiling with 1.6, I get the following compiler error message:

c:\dev\src\misc>javac -source 1.6 Main.java
warning: [options] bootstrap class path not set in conjunction with -source 1.6
Main.java:22: error: unreported exception Exception; must be caught or declared
to be thrown
throw e;
^
1 error
1 warning

But with Java 1.7, it compiles.

c:\dev\src\misc>javac -source 1.7 Main.java

c:\dev\src\misc>

... Until I actually throw an Exception in the try block:

public static void throwsOrNotThrowsThatsTheQuestion() {
try {

// Any processing
throw new IOException("Fake!");

} catch (Exception e) {
throw e;
}

Compiling...

c:\dev\src\misc>javac -source 1.7 Main.java
Main.java:22: error: unreported exception IOException; must be caught or declare
d to be thrown
throw e;
^
1 error

It looks like Java 1.7 got smart enough to detect the kind of Exception(s) that might be thrown by analyzing the try block code, where as 1.6 just saw throw e; of type Exception and gave an error just for that.

Changing it to throw a RuntimeException made it compile as expected, because as always, unchecked Exceptions don't need a throws clause:

public static void throwsOrNotThrowsThatsTheQuestion() {
try {

// Any processing
throw new RuntimeException("Fake!");

} catch (Exception e) {
throw e;
}

Compiling...

c:\dev\src\misc>javac -source 1.7 Main.java

c:\dev\src\misc>

The Explanation

Here's what's going on:

Java 7 introduced more inclusive type checking. Quoting...

Consider the following example:

static class FirstException extends Exception { }
static class SecondException extends Exception { }

public void rethrowException(String exceptionName) throws Exception {
try {
if (exceptionName.equals("First")) {
throw new FirstException();
} else {
throw new SecondException();
}
} catch (Exception e) {
throw e;
}
}

This examples's try block could throw either FirstException or SecondException. Suppose you want to specify these exception types in the throws clause of the rethrowException method declaration. In releases prior to Java SE 7, you cannot do so. Because the exception parameter of the catch clause, e, is type Exception, and the catch block rethrows the exception parameter e, you can only specify the exception type Exception in the throws clause of the rethrowException method declaration.

However, in Java SE 7, you can specify the exception types FirstException and SecondException in the throws clause in the rethrowException method declaration. The Java SE 7 compiler can determine that the exception thrown by the statement throw e must have come from the try block, and the only exceptions thrown by the try block can be FirstException and SecondException. Even though the exception parameter of the catch clause, e, is type Exception, the compiler can determine that it is an instance of either FirstException or SecondException:

(emphasis mine)

public void rethrowException(String exceptionName)
throws FirstException, SecondException {
try {
// ...
}
catch (Exception e) {
throw e;
}
}

Why does the compiler allow throws when the method will never throw the Exception

The throws clause is part of the method's contract. It requires the caller of the method to behave as if the specified exception may be thrown by the method (i.e. either catch the exception or declare their own throws clause).

It's possible that the initial version of a method does not throw the exception specified in the throws clause, but a future version can throw it without breaking the API (i.e. any existing code that calls the method will still pass compilation).

The opposite it also possible. If the method used to throw the exception specified in the throws clause, but a future version of it doesn't throw it anymore, you should keep the throws clause in order not to break existing code that uses your method.

First example:

Suppose you have this code which uses methodB:

private static void methodA() {
methodB(); // doesn't have throws IOException clause yet
}

If later you want to change methodB to throw IOException, methodA will stop passing compilation.

Second example:

Suppose you have this code which uses methodB:

private static void methodA() {
try {
methodB(); // throws IOException
}
catch (IOException ex) {

}
}

If you remove the throws clause from a future version of methodB, methodA won't pass compilation anymore.

This example is not very interesting when methodA is private, because it can only be used locally (within the same class, where it's easy to modify all the methods that call it).

However, if it becomes public, you don't know who uses (or will use) your method, so you have no control of all the code that may break as a result of adding or removing the throws clause.

And if it's an instance method, there's another reason for allowing the throws clause even if you don't throw the exception - the method can be overridden, and the overriding method may throw the exception even if the base class implementation does not.

Rethrowing Exception without requiring throws Exception?

I believe the very next paragraph of section 11.2.2 answers the question:

A throw statement whose thrown expression is a final or effectively final exception parameter of a catch clause C can throw an exception class E iff:

  • E is an exception class that the try block of the try statement which declares C can throw; and

So, throw e; “can throw” only exceptions which the corresponding try-block “can throw,” where the latter is defined by the actual statements in the try-block.

Obviously an empty try-block does not qualify as a “can throw” section for any exception class. Your second example “can throw” NullPointerException, and since the catch-block “can throw” only the exception that the try-block “can throw,” the catch-block too can throw only the unchecked NullPointerException.

Your third example’s try-block “can throw” java.lang.Exception itself, therefore the catch-block “can throw” java.lang.Exception, so java.lang.Exception must be caught or declared to be thrown.

JDK 1.7 onwards, throwing an exception object from catch block does not require a throws clause!!! Why is this so?

You'll see this if the code in the try block can't throw any checked exception. At that point, the compiler knows that the only kind of exception caught by the catch block has to be an unchecked exception, and so it can therefore be rethrown. Note that if you assigned a different value to ex within the catch block, the compiler would no longer be able to have that assurance. At the moment, ex is effectively final.

If you try to call something that is declared to throw a checked exception within the try block, the code will fail to compile as expected.

For example:

public class ExceptionTest {

public void test() {
try {
foo();
} catch(Exception ex) {
throw ex;
}
}

public void foo() throws java.io.IOException {
}
}

Gives an error of:

ExceptionTest.java:12: error: unreported exception IOException; must be caught or declared to be thrown
throw ex;
^

As for where the exception "goes" - if the code in the try block throws an unchecked exception, it gets propagated as normal. Try this:

public class ExceptionTest {

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

public static void test() {
try {
String x = null;
x.length();
} catch(Exception ex) {
throw ex;
}
}
}

Running that gives the following output, as expected:

Exception in thread "main" java.lang.NullPointerException
at ExceptionTest.test(ExceptionTest.java:10)
at ExceptionTest.main(ExceptionTest.java:4)

JLS 11.2.2 documents what exceptions a statement can throw - your code will only compile if there are no checked exceptions that can be thrown.

Understanding exception handling - Rethrowing exceptions

This is described in JDK 7 Rethrowing Exceptions with More Inclusive Type Checking

public void rethrowException(String exceptionName)
throws FirstException, SecondException {
try {
// ...
}
catch (Exception e) {
throw e;
}
}

The Java SE 7 compiler can determine that the exception thrown by the statement throw e must have come from the try block, and the only exceptions thrown by the try block can be FirstException and SecondException. Even though the exception parameter of the catch clause, e, is type Exception, the compiler can determine that it is an instance of either FirstException or SecondException

This analysis is disabled if the catch parameter is assigned to another value in the catch block. However, if the catch parameter is assigned to another value, you must specify the exception type Exception in the throws clause of the method declaration.

Thrown checked exception without throws declaration in method

This is a feature that was added in Java 7.
The compiler can derive the type of exception if you use a variable from the catch clause to rethrow the exception. Since you have no checked exception to be caught, it knows that e could only be RuntimeException and no throws definition is needed.

More information:
https://docs.oracle.com/javase/7/docs/technotes/guides/language/catch-multiple.html

Declaring checked exception using throws clause that is never thrown in the body of the method

It is a compile-time error if a catch clause can catch checked
exception class E1 and it is not the case that the try block
corresponding to the catch clause can throw a checked exception class
that is a subclass or superclass of E1, unless E1 is Exception or a
superclass of Exception.

This tells you all these are valid:

try { }
catch(Exception e){}

--

try{ }
catch(NullPointerException e) {}

--

try{ }
catch(ArrayIndexOutOfBoundsException e) {}

--

try{ }
catch(RuntimeException e) {}

--

try{ }
catch(Error e) {}

--

try{ }
catch(Throwable e){ }

It's not related to a void block. It's related to an impossible path to your checked subclass of Exception.


What happens with subclasses of Exception? For example IOexception: Those are unreachable catch blocks. Nothing in the try block can lead to that exception, so the compiler just tells you: this block would never be executed, as it would never catch that subException.

The difference with throws: The concept of unreachable code doesn't exist in this context. The possibility of a method throwing an exception doesn't end in the deffinition of the method. For example:

abstract void readFile(String path) throws IOException;

This method doesn't even have a block, as it's an abstract method. It's easy to guess that this line won't ever throw any IOException. But it defines a behaviour for the extensions that will implement it. In order to override it, your method must throw the IOException.

In the same way, if someone overrides your test method:

@Override
void test() throws IOException
{
readFile(file);
}

It's not impossible to happen, in contrary to your first try-catch block.

Java 'throws' clause not needed when throwing a new NPE? and why does adding throws Exception give compilation errors?

RuntimeException is unchecked, so compiler does not warn when you throw a exceptions subclass of RuntimeException. If you need compiler to warn you, then you should use Exception or its subclasses.

1) NullPointerException extends RuntimeException
so compiler does not give any error.

2) Even though your method throws NullPointerException, since you marked the method with throws Exception, compiler warns you to catch it in it's callers.

3) Same as 1st answer

4) Same as 1st answer
IllegalArgumentException extends RuntimeException

5) throwNPE does not throw anything at all.

6) Eventhough you throw a NullPointerException (RuntimeException) within throwNPEWithGenericClause, since you mark the method as checked exception, compiler does not allow.

7, 8) Same as 1st answer. Both runtime exceptions, no need to check.



Related Topics



Leave a reply



Submit