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

1.8.0_451 vs 11.0.27: IncompatibleClassChangeError Wrongly Thrown

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Not an Issue
    • Icon: P4 P4
    • None
    • 11.0.27-oracle
    • hotspot
    • x86_64
    • windows

      ADDITIONAL SYSTEM INFORMATION :
      ```
      OS:
      Operating System Name: Windows 11
      Operating System Architecture: amd64
      Operating System Version: 10.0

      ```

      OpenJDK version:

      ```

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

      java version "11.0.27" 2025-04-15 LTS
      Java(TM) SE Runtime Environment 18.9 (build 11.0.27+8-LTS-232)
      Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11.0.27+8-LTS-232, mixed mode)

      ```
      Output from `java -version`.

      A DESCRIPTION OF THE PROBLEM :
      Given a test case, we found that the execution results of this test case on different versions are different, the simplified test case can be found below. In summary, Hotspot(1.8.0_451) ran normally while Hotspot(11.0.27) threw IncompatibleClassChangeError.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Repoduce:

      ```

      >>> path_to_jdk/hotspot/1.8.0_451/bin/java -cp . TestLauncher

      >>> path_to_jdk/hotspot/11.0.27/bin/java -cp . TestLauncher

      ```

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      According to this interesting result, we first decompiled TestLauncher using javap. The results are as follows:

      ```java
      Classfile /xxx/TestLauncher.class
        Last modified 2025年7月24日; size 536 bytes
        MD5 checksum e4b325e8eeac8e5df91e606cc434ddc6
      public class TestLauncher
        minor version: 0
        major version: 52
        flags: (0x0021) ACC_PUBLIC, ACC_SUPER
        this_class: #2 // TestLauncher
        super_class: #4 // java/lang/Object
        interfaces: 0, fields: 0, methods: 1, attributes: 0
      Constant pool:
         #1 = Utf8 TestLauncher
         #2 = Class #1 // TestLauncher
         #3 = Utf8 java/lang/Object
         #4 = Class #3 // java/lang/Object
         #5 = Utf8 main
         #6 = Utf8 ([Ljava/lang/String;)V
         #7 = Utf8 TestClass1
         #8 = Class #7 // TestClass1
         #9 = Utf8 run
        #10 = Utf8 ()V
        #11 = NameAndType #9:#10 // run:()V
        #12 = Methodref #8.#11 // TestClass1.run:()V
        #13 = Utf8 TestClass2
        #14 = Class #13 // TestClass2
        #15 = Methodref #14.#11 // TestClass2.run:()V
        #16 = Utf8 InvalidTestClass
        #17 = Class #16 // InvalidTestClass
        #18 = Methodref #17.#11 // InvalidTestClass.run:()V
        #19 = Utf8 java/lang/System
        #20 = Class #19 // java/lang/System
        #21 = Utf8 out
        #22 = Utf8 Ljava/io/PrintStream;
        #23 = NameAndType #21:#22 // out:Ljava/io/PrintStream;
        #24 = Fieldref #20.#23 // java/lang/System.out:Ljava/io/PrintStream;
        #25 = Utf8 InvalidTestClass load fail:
        #26 = String #25 // InvalidTestClass load fail:
        #27 = Utf8 java/io/PrintStream
        #28 = Class #27 // java/io/PrintStream
        #29 = Utf8 print
        #30 = Utf8 (Ljava/lang/String;)V
        #31 = NameAndType #29:#30 // print:(Ljava/lang/String;)V
        #32 = Methodref #28.#31 // java/io/PrintStream.print:(Ljava/lang/String;)V
        #33 = Utf8 java/lang/Throwable
        #34 = Class #33 // java/lang/Throwable
        #35 = Utf8 toString
        #36 = Utf8 ()Ljava/lang/String;
        #37 = NameAndType #35:#36 // toString:()Ljava/lang/String;
        #38 = Methodref #34.#37 // java/lang/Throwable.toString:()Ljava/lang/String;
        #39 = Utf8 println
        #40 = NameAndType #39:#30 // println:(Ljava/lang/String;)V
        #41 = Methodref #28.#40 // java/io/PrintStream.println:(Ljava/lang/String;)V
        #42 = Utf8 Code
        #43 = Utf8 StackMapTable
      {
        public static void main(java.lang.String[]);
          descriptor: ([Ljava/lang/String;)V
          flags: (0x0009) ACC_PUBLIC, ACC_STATIC
          Code:
            stack=2, locals=2, args_size=1
               0: invokestatic #12 // Method TestClass1.run:()V
               3: invokestatic #15 // Method TestClass2.run:()V
               6: invokestatic #18 // Method InvalidTestClass.run:()V
               9: goto 31
              12: astore_1
              13: getstatic #24 // Field java/lang/System.out:Ljava/io/PrintStream;
              16: ldc #26 // String InvalidTestClass load fail:
              18: invokevirtual #32 // Method java/io/PrintStream.print:(Ljava/lang/String;)V
              21: getstatic #24 // Field java/lang/System.out:Ljava/io/PrintStream;
              24: aload_1
              25: invokevirtual #38 // Method java/lang/Throwable.toString:()Ljava/lang/String;
              28: invokevirtual #41 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
              31: return
            Exception table:
               from to target type
                   6 9 12 Class java/lang/Throwable
            StackMapTable: number_of_entries = 2
              frame_type = 76 /* same_locals_1_stack_item */
                stack = [ class java/lang/Throwable ]
              frame_type = 18 /* same */
      }

      ```

      We observed that for `TestLauncher.main`, the instruction `TestClass1.run();` uses `invokestatic`, while the resolved target is `TestClass1`, which is an interface. According to method resolution as specified in [JVM SE8 §6.5 invokestatic](https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.invokestatic), "The named method is resolved ([§5.4.3.3](https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3))." Meanwhile, [chapter 5.4.3.3](https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3) states, "1. If C is an interface, method resolution throws an `IncompatibleClassChangeError`." Therefore, for hotspot (1.8.451), an `IncompatibleClassChangeError` should be thrown, rather than the method running normally.

      Similarly, due to [JVM SE11 §6.5 invokestatic](https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-6.html#jvms-6.5.invokeinterface), which states, "The named method is resolved ([§5.4.3.3](https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-5.html#jvms-5.4.3.3), [§5.4.3.4](https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-5.html#jvms-5.4.3.4)),” allowing resolution of methods on interfaces, for hotspot(11.0.27) the test case should execute normally instead of throwing an `IncompatibleClassChangeError`.
      ACTUAL -
      The output is as follows:

      hotspot(1.8.0_451):

      ```

      TestClass1 correctly overrides conflicting methods.
      TestClass2 does not override conflicting methods.
      InvalidTestClass should throw an error for conflicting methods.

      ```

      hotspot(11.0.27):

      ```

      Exception in thread "main" java.lang.IncompatibleClassChangeError: Method 'void TestClass1.run()' must be InterfaceMethodref constant

          at TestLauncher.main(Unknown Source)

      ```

      ---------- BEGIN SOURCE ----------
      The following code can be used to generate the test cases(.class file) that reproduces the above process:

      ```java
      import java.io.File;
      import java.io.FileOutputStream;
      import java.io.IOException;

      public class BytecodeUtil {
          public static void main(String args[]) {
              // create package
              String baseDir = "./your_dir"; // your base dir
              File baseDirFile = new File(baseDir);
              baseDirFile.mkdirs();

              String interface1_bytecodeInStr = "CAFEBABE00000034000701000A496E74657266616365310700010100106A6176612F6C616E672F4F626A6563740700030100076D6574686F643101000328295606010002000400000000000104010005000600000000" ;
              String interface2_bytecodeInStr = "CAFEBABE00000034000701000A496E74657266616365320700010100106A6176612F6C616E672F4F626A6563740700030100076D6574686F643201000328295606010002000400000000000104010005000600000000" ;
              String interface3_bytecodeInStr = "CAFEBABE00000034000701000A496E74657266616365330700010100106A6176612F6C616E672F4F626A6563740700030100076D6574686F643101000328295606010002000400000000000104010005000600000000" ;
              String InvalidTestClass_bytecodeInStr = "CAFEBABE00000034001F010010496E76616C696454657374436C6173730700010100106A6176612F6C616E672F4F626A65637407000301000A496E746572666163653107000501000A496E746572666163653207000701000A496E74657266616365330700090100076D6574686F64310100032829560100076D6574686F64320100042849295601000372756E0100106A6176612F6C616E672F53797374656D0700100100036F75740100154C6A6176612F696F2F5072696E7453747265616D3B0C00120013090011001401003F496E76616C696454657374436C6173732073686F756C64207468726F7720616E206572726F7220666F7220636F6E666C696374696E67206D6574686F64732E0800160100136A6176612F696F2F5072696E7453747265616D0700180100077072696E746C6E010015284C6A6176612F6C616E672F537472696E673B29560C001A001B0A0019001C010004436F6465060100020004000300060008000A000000040401000B000C00000401000D000C00000401000B000E00000009000F000C0001001E000000150002000000000009B200151217B6001DB1000000000000" ;
              String TestClass1_bytecodeInStr = "CAFEBABE00000034001E01000A54657374436C617373310700010100106A6176612F6C616E672F4F626A65637407000301000A496E746572666163653107000501000A496E746572666163653207000701000A496E74657266616365330700090100076D6574686F64310100032829560100076D6574686F643201000372756E0100106A6176612F6C616E672F53797374656D07000F0100036F75740100154C6A6176612F696F2F5072696E7453747265616D3B0C00110012090010001301003354657374436C6173733120636F72726563746C79206F766572726964657320636F6E666C696374696E67206D6574686F64732E0800150100136A6176612F696F2F5072696E7453747265616D0700170100077072696E746C6E010015284C6A6176612F6C616E672F537472696E673B29560C0019001A0A0018001B010004436F6465060100020004000300060008000A000000030401000B000C00000401000D000C00000009000E000C0001001D000000150002000000000009B200141216B6001CB1000000000000" ;
              String TestClass2_bytecodeInStr = "CAFEBABE00000034001C01000A54657374436C617373320700010100106A6176612F6C616E672F4F626A65637407000301000A496E746572666163653107000501000A496E746572666163653207000701000A496E746572666163653307000901000372756E0100032829560100106A6176612F6C616E672F53797374656D07000D0100036F75740100154C6A6176612F696F2F5072696E7453747265616D3B0C000F001009000E001101003154657374436C6173733220646F6573206E6F74206F7665727269646520636F6E666C696374696E67206D6574686F64732E0800130100136A6176612F696F2F5072696E7453747265616D0700150100077072696E746C6E010015284C6A6176612F6C616E672F537472696E673B29560C001700180A00160019010004436F6465060100020004000300060008000A000000010009000B000C0001001B000000150002000000000009B200121214B6001AB1000000000000" ;
              String TestLauncher_bytecodeInStr = "CAFEBABE00000034002C01000C546573744C61756E636865720700010100106A6176612F6C616E672F4F626A6563740700030100046D61696E010016285B4C6A6176612F6C616E672F537472696E673B295601000A54657374436C6173733107000701000372756E0100032829560C0009000A0A0008000B01000A54657374436C6173733207000D0A000E000B010010496E76616C696454657374436C6173730700100A0011000B0100106A6176612F6C616E672F53797374656D0700130100036F75740100154C6A6176612F696F2F5072696E7453747265616D3B0C00150016090014001701001C496E76616C696454657374436C617373206C6F6164206661696C3A200800190100136A6176612F696F2F5072696E7453747265616D07001B0100057072696E74010015284C6A6176612F6C616E672F537472696E673B29560C001D001E0A001C001F0100136A6176612F6C616E672F5468726F7761626C65070021010008746F537472696E6701001428294C6A6176612F6C616E672F537472696E673B0C002300240A002200250100077072696E746C6E0C0027001E0A001C0028010004436F646501000D537461636B4D61705461626C650021000200040000000000010009000500060001002A000000410002000200000020B8000CB8000FB80012A700164CB20018121AB60020B200182BB60026B60029B1000100060009000C00220001002B0000000700024C070022120000" ;

              try (FileOutputStream fos = new FileOutputStream(baseDir + "/" + "interface1.class")) {
                  byte[] interface1_bytecode = hexStringToByteArray(interface1_bytecodeInStr);
                  fos.write(interface1_bytecode);
              } catch (IOException e) {
                  e.printStackTrace();
              }

              try (FileOutputStream fos = new FileOutputStream(baseDir + "/" + "interface2.class")) {
                  byte[] interface2_bytecode = hexStringToByteArray(interface2_bytecodeInStr);
                  fos.write(interface2_bytecode);
              } catch (IOException e) {
                  e.printStackTrace();
              }

              try (FileOutputStream fos = new FileOutputStream(baseDir + "/" + "interface3.class")) {
                  byte[] interface3_bytecode = hexStringToByteArray(interface3_bytecodeInStr);
                  fos.write(interface3_bytecode);
              } catch (IOException e) {
                  e.printStackTrace();
              }

              try (FileOutputStream fos = new FileOutputStream(baseDir + "/" + "InvalidTestClass.class")) {
                  byte[] InvalidTestClass_bytecode = hexStringToByteArray(InvalidTestClass_bytecodeInStr);
                  fos.write(InvalidTestClass_bytecode);
              } catch (IOException e) {
                  e.printStackTrace();
              }

              try (FileOutputStream fos = new FileOutputStream(baseDir + "/" + "TestClass1.class")) {
                  byte[] TestClass1_bytecode = hexStringToByteArray(TestClass1_bytecodeInStr);
                  fos.write(TestClass1_bytecode);
              } catch (IOException e) {
                  e.printStackTrace();
              }

              try (FileOutputStream fos = new FileOutputStream(baseDir + "/" + "TestClass2.class")) {
                  byte[] TestClass2_bytecode = hexStringToByteArray(TestClass2_bytecodeInStr);
                  fos.write(TestClass2_bytecode);
              } catch (IOException e) {
                  e.printStackTrace();
              }

              try (FileOutputStream fos = new FileOutputStream(baseDir + "/" + "TestLauncher.class")) {
                  byte[] TestLauncher_bytecode = hexStringToByteArray(TestLauncher_bytecodeInStr);
                  fos.write(TestLauncher_bytecode);
              } catch (IOException e) {
                  e.printStackTrace();
              }

          }

          public static byte[] hexStringToByteArray(String hexString) {
              int length = hexString.length();
              byte[] byteArray = new byte[length / 2];
              for (int i = 0; i < length; i += 2) {
                  int byteValue = Integer.parseInt(hexString.substring(i, i + 2), 16);
                  byteArray[i / 2] = (byte) byteValue;
              }
              return byteArray;
          }
      }

      ```

      Note: modify the paths according to your needs.

      After obtaining the test cases, you can derive the following test cases through decompilation:

      ```java
      public interface Interface1 {
          void method1();
      }
      ```

      ```java
      public interface Interface2 {
          void method2();
      }
      ```

      ```java
      public interface Interface3 {
          void method1();
      }
      ```

      ```java
      public interface InvalidTestClass extends Interface1, Interface2, Interface3 {
          void method1();

          void method2();

          void method1(int var1);

          static void run() {
              System.out.println("InvalidTestClass should throw an error for conflicting methods.");
          }
      }
      ```

      ```java
      public interface TestClass1 extends Interface1, Interface2, Interface3 {
          void method1();

          void method2();

          static void run() {
              System.out.println("TestClass1 correctly overrides conflicting methods.");
          }
      }
      ```

      ```java
      public interface TestClass2 extends Interface1, Interface2, Interface3 {
          static void run() {
              System.out.println("TestClass2 does not override conflicting methods.");
          }
      }
      ```

      ```java
      public class TestLauncher {
          public static void main(String[] var0) {
              TestClass1.run();
              TestClass2.run();

              try {
                  InvalidTestClass.run();
              } catch (Throwable var2) {
                  System.out.print("InvalidTestClass load fail: ");
                  System.out.println(var2.toString());
              }

          }
      }
      ```
      ---------- END SOURCE ----------

            dholmes David Holmes
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated:
              Resolved: