9

In org.springframework.core.SerializableTypeWrapper (version 5.2.3), there is the following code at line 112:

    if (GraalDetector.inImageCode() || !Serializable.class.isAssignableFrom(Class.class)) {
        // Let's skip any wrapping attempts if types are generally not serializable in
        // the current runtime environment (even java.lang.Class itself, e.g. on Graal)
        return providedType;
    }

I'm curious about the second check (!Serializable.class.isAssignableFrom(Class.class)): is it possible for it to evaluate to true (that is, for Serialazable.class to be not assignable from Class.class)?

Here is what Class#isAssignableFrom() javadoc says:

Determines if the class or interface represented by this Class object is either the same as, or is a superclass or superinterface of, the class or interface represented by the specified Class parameter.

Looking at the code of Class, I see the following:

public final class Class<T> implements java.io.Serializable

So Serializable is a superinterface of Class and should always be assignable from Class. But the check in the Spring code suggests that sometimes it's not.

How come? In what situations can this happen and why don't they violate the Java Language Specification?

Roman Puchkovskiy
  • 11,415
  • 5
  • 36
  • 72
  • 1
    The code comment inside the `if` block would appear relevant: "_Let's skip any wrapping attempts if types are generally not serializable in the current runtime environment (even java.lang.Class itself, e.g. on Graal)_". By "Graal" I assume they mean [GraalVM](https://www.graalvm.org/), which has its [limitations](https://github.com/oracle/graal/blob/master/substratevm/LIMITATIONS.md#serialization) when creating native images. I'm guessing on GraalVM maybe `Class` _does not_ implement `Serializable`? And they use that to test if the current environment is Graal. But I'm not sure. – Slaw Jul 24 '20 at 06:56
  • 1
    @Slaw I've compiled a test program with the following line `System.out.println("Is assignable: " + java.io.Serializable.class.isAssignableFrom(Class.class));`; it outputs `true` both when run with `java` (OpenJDK and GraalVM versions) or when I build a native image from this program with GraalVM's `native-image` tool. So (at least with the current version of Graal), this does not seem to be possible in a native image (unless there are some special conditions...). – Roman Puchkovskiy Jul 24 '20 at 07:56

1 Answers1

2

A custom class loader is a possible (if unlikely) mechanism for the expression to return false. Custom class loaders can do some crazy things, including loading their own versions of standard Java classes. Some things to know about class loaders:

  • Custom class loaders can be configured to load any class, even including Java standard library classes (it's discouraged of course, but still possible)
  • Custom class loaders will typically be configured to delegate to the system class loader for classes that they don't know how to load.
  • When class A has a reference to class B, the reference will be resolved using whichever class loader was used to load class A
  • More than one class loader can be configured to handle the same class, which can lead to multiple versions of a class getting loaded into the JVM, with the actual implementation you get depending on which class loader you ask

Suppose there is a custom class loader that, for whatever reason, is configured to load java.io.Serializable by itself, but delegates to the system class loader for loading other classes, including java.lang.Class.

Now suppose this custom class loader is used to load SerializableTypeWrapper. This means it will also be used to resolve the reference to java.io.Serializable in SerializableTypeWrapper. With the reference to java.lang.Class, the custom class loader will delegate this to the system class loader. The system class loader will be used to load java.lang.Class, but it will also be used to load the reference to java.io.Serializable from within java.lang.Class.

So now we can ask the question - is java.io.Serializable [custom] assignable from java.lang.Class [standard]? And the answer is no - java.lang.Class does implement java.io.Serializable [standard], but it does not implement java.io.Serializable [custom].

Kevin K
  • 9,344
  • 3
  • 37
  • 62
  • Thank you for the explanation. I wonder what `Class.class instanceof Serializable` would produce in your example. It would probably still be `true` because the right-hand side expression is not a class instance (which may be different for different classloaders) but a class name (at runtime, a member of constant pool, probably), is it correct? – Roman Puchkovskiy Aug 14 '20 at 14:52
  • The RHS must be a reference type, not a simple class name string. You're right though about it being looked up in the constant pool. But, the constant pool is per-class, and the lookup is performed using the class loader that loaded the class. So it would be the same result - it'll return `false` if the `Serializable` known to the class loader is different than the `Serializable` implemented by `Class`. – Kevin K Aug 17 '20 at 03:50
  • JVM spec has some good details, if you're interested: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.instanceof Also, on class resolution: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.1 – Kevin K Aug 17 '20 at 03:50