Reputation: 13331
I stumbled upon code looking something like this:
void run() {
try {
doSomething();
} catch (Exception ex) {
System.out.println("Error: " + ex);
throw ex;
}
}
void doSomething() {
throw new RuntimeException();
}
This code surprises me because it looks like the run()
-method is capable of throwing an Exception
, since it catches Exception
and then rethrows it, but the method is not declared to throw Exception
and apparently doesn't need to be. This code compiles just fine (in Java 11 at least).
My expectation would be that I would have to declare throws Exception
in the run()
-method.
Extra information
In a similar way, if doSomething
is declared to throw IOException
then only IOException
needs to be declared in the run()
-method, even though Exception
is caught and rethrown.
void run() throws IOException {
try {
doSomething();
} catch (Exception ex) {
System.out.println("Error: " + ex);
throw ex;
}
}
void doSomething() throws IOException {
// ... whatever code you may want ...
}
Question
Java usually likes clarity, what is the reason behind this behavior? Has it always been like this? What in the Java Language Specification allows the run()
method not need to declare throws Exception
in the code snippets above? (If I would add it, IntelliJ warns me that Exception
is never thrown).
Upvotes: 11
Views: 2183
Reputation: 1978
Exception
is the parent of all exceptions, including RuntimeException
, which is unchecked and therefore allowed to be thrown from anywhere. The code in question will allow you to manipulate any unchecked exception (catching and re-throwing), using their common parent, which is questionable, but still ok since it produces only unchecked ones. However, as soon as the compiler detects the use of a checked exception (including the Exception
itself), the compilation fails.
This compiles and passes:
@Test
public void test() {
Runnable underTest = new Runnable() {
@Override
public void run() {
try{
doSomething();
} catch (Exception e) {
throw e;
};
}
private void doSomething() {
throw new RuntimeException("test");
}
};
assertThatThrownBy(underTest::run)
.isInstanceOf(Exception.class) // RuntimeException.class also ok
.hasMessage("test");
}
This doesn't compile:
@Test
public void test() {
Runnable underTest = new Runnable() {
@Override
public void run() {
try{
doSomething();
} catch (Exception e) {
throw e;
};
}
private void doSomething() throws Exception {
throw new Exception("test");
}
};
assertThatThrownBy(underTest::run)
.isInstanceOf(Exception.class)
.hasMessage("test");
}
Upvotes: 0
Reputation: 21
Unchecked exceptions do not need to be declared in a method or constructor's throws clause if they can be thrown by the execution of the method or constructor and propagate outside the method or constructor boundary. RuntimeException is unchecked.
Upvotes: -1
Reputation: 120848
I have not scan through the JLS
as you have asked in your question, so please take this answer with a grain of salt. I wanted to make it a comment, but it would have been too big.
I find funny at times, how javac
is pretty "smart" in some cases (like in your case), but leaves a lot of other things to be handled later by JIT
. In this case, it is just that the compiler "can tell" that only a RuntimeException
would be caught. This is obvious, it's the only thing you throw in doSomething
. If you slightly change your code to:
void run() {
try {
doSomething();
} catch (Exception ex) {
Exception ex2 = new Exception();
System.out.println("Error: " + ex);
throw ex2;
}
}
you will see a different behavior, because now javac
can tell that there is a new Exception
that you are throwing, un-related to the one you caught.
But things are far from ideal, you can "trick" the compiler yet again via:
void run() {
try {
doSomething();
} catch (Exception ex) {
Exception ex2 = new Exception();
ex2 = ex;
System.out.println("Error: " + ex);
throw ex2;
}
}
IMO, because of ex2 = ex;
it should not fail again, but it does.
Just in case this was compiled with javac 13+33
Upvotes: 0