JDK-8215328 : NullPointerException thrown by default type implementations from lambda on calling #toString
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.lang:reflect
  • Affected Version: 8,11,12
  • Priority: P3
  • Status: Resolved
  • Resolution: Duplicate
  • OS: generic
  • CPU: x86_64
  • Submitted: 2018-12-11
  • Updated: 2019-01-18
  • Resolved: 2019-01-18
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 13
13Resolved
Related Reports
Duplicate :  
Duplicate :  
Description
ADDITIONAL SYSTEM INFORMATION :
java version "11.0.1" 2018-10-16 LTS
Java(TM) SE Runtime Environment 18.9 (build 11.0.1+13-LTS)
Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11.0.1+13-LTS, mixed mode)

Windows 10 Pro Version 1803
Build 17134.407

A DESCRIPTION OF THE PROBLEM :
If you initialize a parameterized type with a type variable as its type argument, you can, at least if you initialize a subclass of it, refer to this type variable using the default java.lang.reflect.ParameterizedType class. You can gather an instance of it by calling Class#getGenericSupertype (several times) and cast the result to ParameterizedType. This also works fine if you e.g. provide a generic array type holding the type variable as its component type. In this case, the first index of the array representing the actual type arguments is a GenericArrayType holding a TypeVariable.

However, if you initialize the same type from a lambda expression using a type variable provided by a parameterized method, the component type doesn't refer to the type variable anymore, but to the null type instead. As a consequence, the #toString method starts to throw NullPointerException s. If you do the same with just a single TypeVariable (not inside any other container type like ParameterizedType), the type variable is null.

This problem has already been discussed here (https://stackoverflow.com/questions/53039980/different-generic-behaviour-when-using-lambda-instead-of-explicit-anonymous-inne) and it seems like that this behaviour is caused by the way lambdas are handled by the compiler (creating a synthetic method without a generic signature). Even if this behaviour might be acceptable, the #toString method should never throw an exception.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Initialize a parameterized type from a lambda context. You have to pass a type variable to the types' parameters; the place of its declaration doesn't matter.
2. Get the type information via the java.lang.reflection APIs. As you'll the, the type variable becomes null. 

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Printing T and T[] since these are the types that have actually been passed to the parameterized type.

I'm unsure whether this functionality can be provided by the current implementation scheme since the lambda body gets extracted to an extra method. 
Possible solutions would be:
- Add generic type signatures to the synthetic methods
- Use the types stored in the ConstantPool. However, since this approach requires an extra-corner-case for lambdas, I'd recommend the first solution. 
ACTUAL -
null and NullPointerException

---------- BEGIN SOURCE ----------
import java.lang.reflect.ParameterizedType; 

import java.lang.reflect.Type; 

public class Test { 
    private class A<T> { 
        private Type type; 

        private A() { 
            ParameterizedType paramType = (ParameterizedType) this.getClass().getGenericSuperclass(); 
            this.type = paramType.getActualTypeArguments()[0]; 
        } 

        public Type getType() { 
            return type; 
        } 
    } 

    <T> void success() { 
        Type typeVariable = new A<T>() { 
        }.getType(); 
        Type typeArrVariable = new A<T[]>() { 
        }.getType(); 
        System.out.println(typeVariable); 
        System.out.println(typeArrVariable); 
    } 

    <T> void fails() { 
        Runnable exec = () -> { 
            Type typeVariable = new A<T>() {}.getType(); 
            Type typeArrVariable = new A<T[]>() {}.getType(); 
            System.out.println(typeVariable); 
            try { 
                System.out.println(typeArrVariable); 
            }catch (NullPointerException e) { 
                System.out.println("Generic Type Array failed"); 
                e.printStackTrace(); 
            } 
        }; 
        exec.run(); 
    } 

    public static void main(String[] args) {
        Test obj = new Test();
        obj.success();
        obj.fails();
    }
}
---------- END SOURCE ----------

FREQUENCY : always