JDK-8178523 : Generic type variable information for lambdas is not preserved at runtime.
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.lang
  • Affected Version: 8
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • OS: generic
  • CPU: generic
  • Submitted: 2017-04-11
  • Updated: 2017-04-21
Description
FULL PRODUCT VERSION :
java version "1.8.0_121"
Java(TM) SE Runtime Environment (build 1.8.0_121-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode)

ADDITIONAL OS VERSION INFORMATION :
Linux omen 4.8.14-200.fc24.x86_64 #1 SMP Mon Dec 12 16:31:37 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux

A DESCRIPTION OF THE PROBLEM :
Unlike generically typed anonymous classes, generically typed lambda expressions do not preserve type variable information at runtime. For example,

   public interface ContextResolver<T> {
      T getContext(Class<?> type);
   }

   @Test
   public void test() {
      ContextResolver<String> resolver = ((ContextResolver<String>)type -> "foo");
      for (Type type : resolver.getClass().getGenericInterfaces()) {
         System.out.println("type: " + type);
      }
   }

outputs

type: interface javax.ws.rs.ext.ContextResolver

but running

   @Test
   public void test() {
      ContextResolver<String> resolver1 = new ContextResolver<String>() {
         @Override
         public String getContext(Class<?> type) {
            return null;
         }
      };
      for (Type type : resolver.getClass().getGenericInterfaces()) {
         System.out.println("type: " + type);
      }
   }

outputs

type: javax.ws.rs.ext.ContextResolver<java.lang.String>

There is a known solution to this problem, found here: https://github.com/jhalterman/typetools (specifically, in TypeResolver, here: https://github.com/jhalterman/typetools/blob/master/src/main/java/net/jodah/typetools/TypeResolver.java).

One problem seems to be the reference to a java.sun class:

String constantPoolName = JAVA_VERSION < 9 ? "sun.reflect.ConstantPool" : "jdk.internal.reflect.ConstantPool";

Note that the fix works for

 * java-1.8.0-openjdk-1.8.0.121-8.b14.fc24.x86_64
 * jdk1.8.0_121 (oracle: build 25.121-b13)
 * openjdk 9 (build 9-ea+163)

but it doesn't work for

 * ibm JRE 1.8.0 (build 2.8)

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Just run the example above.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
javax.ws.rs.ext.ContextResolver<java.lang.String>
ACTUAL -
type: interface javax.ws.rs.ext.ContextResolver

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
public class ContextResolverTest {

   public static void main(String[] args) {
      ContextResolver<String> resolver = ((ContextResolver<String>)type -> "foo");
      for (Type type : resolver.getClass().getGenericInterfaces())
      {
         System.out.println("type: " + type);
      }
   }
}
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
With some JDKs, it's possible to use the above mentioned TypeResolver to retrieve the value of type variables at runtime:

    @Test
    public void test()
    {
       MapFunction<String, Integer> fn = str -> Integer.valueOf(str);
       Class<?>[] typeArgs = TypeResolver.resolveRawArguments(MapFunction.class, fn.getClass());
       for (Class<?> clazz : typeArgs)
       {
          System.out.println(clazz);
       }
    }

outputs

class java.lang.String
class java.lang.Integer


Comments
To reproduce the issue, run the attached test case: JDK 8 - Fail JDK 8u121 - Fail output: type: interface javax.ws.rs.ext.ContextResolver
12-04-2017