Exception in ChangeListener

| tagged as

Today I had an interesting issue with JavaFX ChangeListeners and Exceptions. I was writing a unit test to reproduce a simple bug in mvvmFX. The issue was a NullPointerException that was thrown under some specific conditions. No big deal. I had written the test case that reproduces the exception in no time but something was still wrong. The exception was visible in the console window of the IDE but the test was still green.

green test and still an exception

The problem was that the exception was thrown inside of a JavaFX ChangeListener. The question ist: Why is the exception visible in the console window but doesn't crash the current running Thread?

To find an answer I was digging in the depth of the JavaFX source code (I love open source).

And there it is: In StringPropertyBase.java an ExpressionHelper is handling added Listeners. Every ExpressionHelper has a fireValueChangedEvent method that is responsible for calling the registered listener. And there you find this interesting code:

try {
    listener.changed(observable, oldValue, currentValue);
} catch (Exception e) {
    Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), e);
}

The line in the catch clause is interesting. In Java you can define an "UncaughtExceptionHandler" that is responsible for handling exceptions that aren't caught by the application itself. This can be useful when you don't want to crash the whole JVM when an exception wasn't properly handled. For example think of a servlet container that shouldn't be terminated when one of the servlets throws an exception.

But in our use case we don't want to hold back the exception. We want to see our test case fail if there is an exception thrown. So what is the solution? Look at this code:

import static org.assertj.core.api.Assertions.fail;

@Test
public void test() throws Exception {
    Thread.currentThread().setUncaughtExceptionHandler((thread, exception) -> fail("Exception was thrown", exception));

    Example example = new Example();
    example.value.setValue("test");
}

Now the test fails as expected. We define our own "UncaughtExceptionHandler" with a lambda expression. Inside of the lambda the "fail" method of AssertJ is called which leads to a test failure.