64

I've noticed something weird about unhandled exceptions using Java 8 method reference. This is my code, using the lambda expression () -> s.toLowerCase():

public class Test {
 public static void main(String[] args) {
 testNPE(null);
 }
 private static void testNPE(String s) {
 Thread t = new Thread(() -> s.toLowerCase());
// Thread t = new Thread(s::toLowerCase);
 t.setUncaughtExceptionHandler((t1, e) -> System.out.println("Exception!"));
 t.start();
 }
}

It prints "Exception", so it works fine. But when I change Thread t to use a method-reference (even IntelliJ suggests that):

Thread t = new Thread(s::toLowerCase);

the exception is not being caught:

Exception in thread "main" java.lang.NullPointerException
 at Test.testNPE(Test.java:9)
 at Test.main(Test.java:4)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at java.lang.reflect.Method.invoke(Method.java:497)
 at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

Can someone explain what is going on here?

Tunaki
138k46 gold badges372 silver badges446 bronze badges
asked May 24, 2016 at 12:07
4
  • I've checked in Eclipse my self and it works fine. Ideone.com throws NPE too Commented May 24, 2016 at 12:15
  • 1
    Check this out : ideone.com/nPvWex Commented May 24, 2016 at 12:22
  • 5
    lambdas are evaluated when executed, function references are evaluated when declared. Commented May 24, 2016 at 15:20
  • @njzk2 short and simple, thanks. Although it technically makes sense, it is very sneaky Commented Oct 5, 2023 at 11:42

2 Answers 2

71

This behaviour relies on a subtle difference between the evaluation process of method-references and lambda expressions.

From the JLS Run-Time Evaluation of Method References:

First, if the method reference expression begins with an ExpressionName or a Primary, this subexpression is evaluated. If the subexpression evaluates to null, a NullPointerException is raised, and the method reference expression completes abruptly.

With the following code:

Thread t = new Thread(s::toLowerCase); // <-- s is null, NullPointerException thrown here
t.setUncaughtExceptionHandler((t1, e) -> System.out.println("Exception!"));

the expression s is evaluated to null and an exception is thrown exactly when that method-reference is evaluated. However, at that time, no exception handler was attached, since this code would be executed after.

This doesn't happen in the case of a lambda expression, because the lambda will be evaluated without its body being executed. From Run-Time Evaluation of Lambda Expressions:

Evaluation of a lambda expression is distinct from execution of the lambda body.

Thread t = new Thread(() -> s.toLowerCase());
t.setUncaughtExceptionHandler((t1, e) -> System.out.println("Exception!"));

Even if s is null, the lambda expression will be correctly created. Then the exception handler will be attached, the thread will start, throwing an exception, that will be caught by the handler.


As a side-note, it seems Eclipse Mars.2 has a small bug regarding this: even with the method-reference, it invokes the exception handler. Eclipse isn't throwing a NullPointerException at s::toLowerCase when it should, thus deferring the exception later on, when the exception handler was added.

answered May 24, 2016 at 12:26
Sign up to request clarification or add additional context in comments.

6 Comments

A more theoretical way to reading this is: expressions are evaluated to "normal forms" (i.e. values). It so happens that lambdas' normal forms do not evaluate the body until called (also known as weak head normal form WHNF). This means that the value "error" is different from "() -> error" because the latter produces the error only when calling the function. This trick is also used to write fixpoint combinators in eager languages: you take the lazy combinator fix f = f (fix f) and add a lambda abstraction to introduce lazyness fix f = x -> f (fix f) x.
Not only does the exception occur before the uncaught-exception handler is set, the exception happens in the main thread instead of t.
"This doesn't happen in the case of a lambda expression, because the lambda will be evaluated without its body being executed." This part suggests that method reference evaluation is contrary to lambda expression evaluation and that method reference evaluation invokes referenced method. Method reference evaluation just have that additional step of veryfing subexpression, but that verification is not part of invocation, but it's part of an evaluation. From JLS: "Evaluation of a method reference expression is distinct from invocation of the method itself.". Anyway, great post.
@Martin the behavior is the same as with someObject.new InnerClass(). On a semantic level, when you say list.iterator(), it also doesn’t matter whether you’ll ever use the iterator, it will fail immediately when list is null. This applies to all kind of bindings. Whereas () -> s.toLowerCase() doesn’t bind s, it will reevaluate it every time the function is evaluated. In other words, x -> System.out.println(x) doesn’t bind System.out, but reevaluates it every time, reflecting when someone called System.setOut in the meanwhile. In contrast to System.out::println.
|
7

Wow. You have uncovered something interesting. Let us take a look at the following:

Function<String, String> stringStringFunction = String::toLowerCase;

This returns us a function which accepts on parameter of type String and returns another String, which is a lowercase of the input parameter. This is somewhat equivalent to the s.toLowerCase(), where s is the input parameter.

stringStringFunction(param) === param.toLowerCase()

Next

Function<Locale, String> localeStringFunction = s::toLowerCase;

is a function from Locale to String. This is equivalent to s.toLowerCase(Locale) method invocation. It works, under the hood, on 2 parameters: one is s and another is some locale. If s is null, then this function creation throws a NullPointerException.

localeStringFunction(locale) === s.toLowerCase(locale)

Next is

Runnable r = () -> s.toLowerCase()

Which is an implementation of Runnable interface which, when executed, will call method toLowerCase on given string s.

So in your case

Thread t = new Thread(s::toLowerCase);

tries to create a new Thread passing the result of the invocation of s::toLowerCase to it. But this throws a NPE at once. Even before thread is started. And so NPE is thrown in your current thread, not from inside thread t. This is why your exception handler is not executed.

answered May 24, 2016 at 12:32

1 Comment

"the result of the invocation of s::toLowerCase" needs a clarification. The creation of the function instance is not an invocation.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.