How can Serializable.class not be assignable from Class.class?

Viewed 219

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?

1 Answers

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].

Related