JDK-8364108 : 1.8.0_451 vs 11.0.27: IncompatibleClassChangeError Wrongly Thrown
  • Type: Bug
  • Component: hotspot
  • Sub-Component: runtime
  • Affected Version: 11.0.27-oracle
  • Priority: P4
  • Status: Closed
  • Resolution: Not an Issue
  • OS: windows
  • CPU: x86_64
  • Submitted: 2025-07-25
  • Updated: 2025-07-28
  • Resolved: 2025-07-28
Related Reports
Relates :  
Relates :  
Description
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 ----------


Comments
The basic scenario is that we have an invokestatic of an interface static method that uses a MethodRef not an InterfaceMethodRef. Static interface methods were added in Java 8. In JVMS 8, as noted above, invokestatic applies method resolution, which indicates the throwing of IncompatibleClassChangeError if the target type is an interface. But hotspot does not distinguish between a MethodRef and an InterfaceMethodRef in this case and allows the invocation to proceed. This was previously reported as JDK-8273407 and it was determined this would not be fixed in any Java 8 update release due to compatibility concerns. From Java 9 (JVMS 9) the invokestatic text was updated to allow for resolution to apply either method resolution (5.4.3.3), or interface method resolution (5.4.3.4) as applicable; and further that using a MethodRef for an interface method (and vice-versa) should throw IncompatibleClassChangeError - JDK-8145148. Hence the behaviour observed in JDK 11. This behaviour is specified in JVMS 4.4.2: - The class_index item of a CONSTANT_Methodref_info structure must be a class type, not an interface type. - The class_index item of a CONSTANT_InterfaceMethodref_info structure must be an interface type. but was not enforced until Java 9. So closing this as "Not an issue".
28-07-2025

I = Correct Result L = Can be reproduced from the provided reproducer W = no workaround ILW = MLM = P4
25-07-2025

Reproduce the issue =============== C:\java -showversion -cp . TestLauncher java version "1.8.0_451" Java(TM) SE Runtime Environment (build 1.8.0_451-b92) Java HotSpot(TM) 64-Bit Server VM (build 25.451-b92, mixed mode) TestClass1 correctly overrides conflicting methods. TestClass2 does not override conflicting methods. InvalidTestClass should throw an error for conflicting methods. C:\java -showversion -cp . TestLauncher 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) Exception in thread "main" java.lang.IncompatibleClassChangeError: Method 'void TestClass1.run()' must be InterfaceMethodref constant at TestLauncher.main(Unknown Source)
25-07-2025