Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8284984

ObjectInputStream throws ClassNotFoundException to class already loaded

XMLWordPrintable

      ADDITIONAL SYSTEM INFORMATION :
      macOS Big Sur 11.1

      java version "1.8.0_291"
      Java(TM) SE Runtime Environment (build 1.8.0_291-b10)
      Java HotSpot(TM) 64-Bit Server VM (build 25.291-b10, mixed mode)

      Also

      Mac OS Catalina 10.15.7

      java version "1.8.0_161"
      Java(TM) SE Runtime Environment (build 1.8.0_161-b12)
      Java HotSpot(TM) 64-Bit Server VM (build 25.161-b12, mixed mode)


      A DESCRIPTION OF THE PROBLEM :
      Under some circumstances ObjectInputStream.readObject() will throw a ClassNotFoundException even though the class of the object being read is already loaded.

      This happens in the following circumstances:
      - S wishes to send R an object O of class C
      - C is not in R's class path
      - R has access to a user defined class loader that can load C from an external source
      - S knows that R does not have C loaded
      - S sends R an out-of-band message to load C
      - R loads C and acknowledges to S
      - S sends R the object
      - on attempting to read the object R receives a ClassNotFoundException

      The issue arises in the ObjectInputStream.resolveClass() method. This method is called by readObject() as part of the object read process. The resolveClass() method contains the following line:

      return Class.forName(name, false, latestUserDefinedLoader());

      where latestUserDefinedLoader() seems to be a heuristic for determining the loader most likely to have loaded the class being read.

      In the circumstances above the wrong loader is returned causing the Class.forName() call to return the ClassNotFoundException. The loader required is the one R used to load C. What loader is returned by the latestUserDefinedLoader() call is unknown to me.

      I have proven this is the problem but subclassing ObjectInputStream and overriding the resolveClass() method as below.

      With this code in place the loader that R used to load C is used by resolveClass() and the object is correctly read and returned.

      The solution would appear to be to provide an alternative readObject() method that allows the caller to specify the class loader to be used to resolve the class.


      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      As above

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      Object sent by S
      ACTUAL -
      ClassNotFoundException

      ---------- BEGIN SOURCE ----------
      Too complex - requires sender and receiver code, a codebase, knowledge of the tester's platform and file system, ...
      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      ----------- Code that bypasses the problem by subclassing ObjectInputStream ----------------

      Usage:
      - call setDoingOISRead() immediately before attempting to read C, nominating the class loader used to load C
      - call clearDoingOISRead() immediately after the read returns


      package q.simpleCommon.simpleClasses;

      // An override of ObjectInputStream that allows us to specify the class loader to be used to
      // resolve a class that is being downloaded via an object input stream
      //
      // This class is intended to be used to overcome a problem experienced by Java method caller where the classes to
      // be used in a method call are preloaded to prevent ClassNotFound errors in downloading the parameter
      // objects but the actual download of the request object (which contains the parameter objects) fails because
      // ObjectInputStream tries to resolve the downloaded object classes using a specific class loader that
      // doesn't know about them rather than the loader we used to preload them.
      //
      // During the period that doingOISRead the OIS method resolve() will use the loaderToUseForObjectInputStreamRead
      // for all class resolves and that method is used for things other than downloads of objects; so
      // doingOISRead should be set just ahead of the intended read and cleared just after
      //

      import java.io.IOException;
      import java.io.InputStream;
      import java.io.ObjectInputStream;
      import java.io.ObjectStreamClass;

      public class APILoaderObjectInputStream extends ObjectInputStream {

          public ClassLoader loaderToUseForObjectInputStreamRead = null;
          public boolean doingOISRead = false;
          
          public APILoaderObjectInputStream(InputStream inputStream) throws IOException {
              super(inputStream);
          }
          
          public void setDoingOISRead(ClassLoader loaderToUseForObjectInputStreamRead) {
              doingOISRead = true;
              this.loaderToUseForObjectInputStreamRead = loaderToUseForObjectInputStreamRead;
          }
          
           public void clearDoingOISRead() {
              doingOISRead = false;
          }

          @Override
          protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
              
              // Override the resolve class method and use our own class loader (but only if doingOISRead is
              // currently set)
              if (!doingOISRead) {
                  return super.resolveClass(desc);
              }
              if (loaderToUseForObjectInputStreamRead == null) {
                  throw new RuntimeException("Loader to use has not been set");
              }
              String name = desc.getName();
              try {
                  Class<?> result = Class.forName(name, false, loaderToUseForObjectInputStreamRead);
                  return result;
              } catch (ClassNotFoundException e) {
                  return super.resolveClass(desc); // Will probably give the same result
              }
          }
      }

      FREQUENCY : always


            rriggs Roger Riggs
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated:
              Resolved: