JDK-8356942 : invokeinterface Throws AbstractMethodError Instead of IncompatibleClassChangeError
  • Type: Bug
  • Component: hotspot
  • Sub-Component: runtime
  • Affected Version: 10
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • OS: windows
  • CPU: x86_64
  • Submitted: 2025-05-12
  • Updated: 2025-07-22
  • Resolved: 2025-07-14
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 25 JDK 26
25Fixed 26 b07Fixed
Related Reports
Causes :  
Description
ADDITIONAL SYSTEM INFORMATION :
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_441"
Java(TM) SE Runtime Environment (build 1.8.0_441-b07)
Java HotSpot(TM) 64-Bit Server VM (build 25.441-b07, mixed mode)

java version "11.0.26" 2025-01-21 LTS
Java(TM) SE Runtime Environment 18.9 (build 11.0.26+7-LTS-187)
Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11.0.26+7-LTS-187, mixed mode)

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_441) threw IncompatibleClassChangeError while the other threw AbstractMethodError. 

REGRESSION : Last worked in version 11

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
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
        String packageName = "P0";
        File folder = new File(baseDir + "/" + packageName);
        folder.mkdirs();

        String P0_I0_bytecodeInStr = "CAFEBABE00000034000F01000550302F49300700010100106A6176612F6C616E672F4F626A6563740700030100016D01001528294C6A6176612F6C616E672F496E74656765723B0100116A6176612F6C616E672F496E746567657207000703000000000100063C696E69743E010004284929560C000A000B0A0008000C010004436F64650601000200040000000000010001000500060001000E00000016000300010000000ABB0008591209B7000DB0000000000000" ;
        String P0_C1_bytecodeInStr = "CAFEBABE00000034000E01000550302F43310700010100106A6176612F6C616E672F4F626A65637407000301000550302F493007000501000550302F49320700070100063C696E69743E0100032829560C0009000A0A0004000B010004436F64650021000200040002000600080000000100010009000A0001000D0000001100010001000000052AB7000CB1000000000000" ;
        String P0_Helper_bytecodeInStr = "CAFEBABE00000034001901000950302F48656C7065720700010100106A6176612F6C616E672F4F626A6563740700030100063C696E69743E0100032829560C000500060A00040007010005676574433101000928294C50302F43313B01000550302F433107000B0A000C0007010005676574433301000928294C50302F43333B01000550302F43330700100A00110007010005676574433401000928294C50302F43343B01000550302F43340700150A00160007010004436F6465002100020004000000000004000100050006000100180000001100010001000000052AB70008B10000000000090009000A0001001800000016000200010000000ABB000C59B7000D4B2AB0000000000009000E000F0001001800000016000200010000000ABB001159B700124B2AB0000000000009001300140001001800000016000200010000000ABB001659B700174B2AB0000000000000" ;
        String P0_I2_bytecodeInStr = "CAFEBABE00000034000F01000550302F49320700010100106A6176612F6C616E672F4F626A6563740700030100016D01001528294C6A6176612F6C616E672F496E74656765723B0100116A6176612F6C616E672F496E746567657207000703000000020100063C696E69743E010004284929560C000A000B0A0008000C010004436F64650601000200040000000000010001000500060001000E00000016000300010000000ABB0008591209B7000DB0000000000000" ;
        String P0_C3_bytecodeInStr = "CAFEBABE00000034002D01000550302F43330700010100106A6176612F6C616E672F4F626A65637407000301000550302F49300700050100063C696E69743E0100032829560C000700080A000400090100047465737401001528294C6A6176612F6C616E672F496E74656765723B01000950302F48656C70657207000D010005676574433401000928294C50302F43343B0C000F00100A000E00110100016D0C0013000C0B000600140100046D61696E010016285B4C6A6176612F6C616E672F537472696E673B29560C000B000C0A000200180100106A6176612F6C616E672F53797374656D07001A0100036F75740100154C6A6176612F696F2F5072696E7453747265616D3B0C001C001D09001B001E01000E52657475726E2076616C75653A200800200100136A6176612F696F2F5072696E7453747265616D0700220100057072696E74010015284C6A6176612F6C616E672F537472696E673B29560C002400250A002300260100077072696E746C6E010015284C6A6176612F6C616E672F4F626A6563743B29560C002800290A0023002A010004436F646500210002000400010006000000030001000700080001002C0000001100010001000000052AB7000AB1000000000009000B000C0001002C00000017000100010000000BB800124B2AB900150100B0000000000009001600170001002C0000001E0003000200000012B800194CB2001F591221B600272BB6002BB1000000000000" ;
        String P0_C4_bytecodeInStr = "CAFEBABE00000034000E01000550302F43340700010100106A6176612F6C616E672F4F626A65637407000301000550302F493007000501000550302F49350700070100063C696E69743E0100032829560C0009000A0A0004000B010004436F64650021000200040002000600080000000100010009000A0001000D0000001100010001000000052AB7000CB1000000000000" ;
        String P0_I5_bytecodeInStr = "CAFEBABE00000034000F01000550302F49350700010100106A6176612F6C616E672F4F626A6563740700030100016D01001528294C6A6176612F6C616E672F496E74656765723B0100116A6176612F6C616E672F496E746567657207000703000000050100063C696E69743E010004284929560C000A000B0A0008000C010004436F64650601000200040000000000010001000500060001000E00000016000300010000000ABB0008591209B7000DB0000000000000" ;

        try (FileOutputStream fos = new FileOutputStream(folder.getAbsolutePath() + "/" + "I0.class")) {
            byte[] P0_I0_bytecode =  hexStringToByteArray(P0_I0_bytecodeInStr);
            fos.write(P0_I0_bytecode);
        } catch (IOException e) {
            e.printStackTrace();
        }

        try (FileOutputStream fos = new FileOutputStream(folder.getAbsolutePath() + "/" + "C1.class")) {
            byte[] P0_I0_bytecode =  hexStringToByteArray(P0_C1_bytecodeInStr);
            fos.write(P0_I0_bytecode);
        } catch (IOException e) {
            e.printStackTrace();
        }

        try (FileOutputStream fos = new FileOutputStream(folder.getAbsolutePath() + "/" + "Helper.class")) {
            byte[] P0_Helper_bytecode =  hexStringToByteArray(P0_Helper_bytecodeInStr);
            fos.write(P0_Helper_bytecode);
        } catch (IOException e) {
            e.printStackTrace();
        }

        try (FileOutputStream fos = new FileOutputStream(folder.getAbsolutePath() + "/" + "I2.class")) {
            byte[] P0_I2_bytecode =  hexStringToByteArray(P0_I2_bytecodeInStr);
            fos.write(P0_I2_bytecode);
        } catch (IOException e) {
            e.printStackTrace();
        }

        try (FileOutputStream fos = new FileOutputStream(folder.getAbsolutePath() + "/" + "C3.class")) {
            byte[] P0_C3_bytecode =  hexStringToByteArray(P0_C3_bytecodeInStr);
            fos.write(P0_C3_bytecode);
        } catch (IOException e) {
            e.printStackTrace();
        }

        try (FileOutputStream fos = new FileOutputStream(folder.getAbsolutePath() + "/" + "C4.class")) {
            byte[] P0_C4_bytecode =  hexStringToByteArray(P0_C4_bytecodeInStr);
            fos.write(P0_C4_bytecode);
        } catch (IOException e) {
            e.printStackTrace();
        }

        try (FileOutputStream fos = new FileOutputStream(folder.getAbsolutePath() + "/" + "I5.class")) {
            byte[] P0_I5_bytecode =  hexStringToByteArray(P0_I5_bytecodeInStr);
            fos.write(P0_I5_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.

Repoduce:

>>> path_to_jdk/Hotspot_1.8.0_441/bin/java -cp . P0.C3

>>> path_to_jdk/Hotspot_11.0.26/bin/java -cp . P0.C3

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

```java
Classfile /D:/Lab/TestJDoc/JSmith-main/your_dir/P0/C3.class
  Last modified May 12, 2025; size 539 bytes
  MD5 checksum a0ff4ab4a79276d3a778adc7888a35bf
public class P0.C3 implements P0.I0
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Utf8               P0/C3
   #2 = Class              #1             // P0/C3
   #3 = Utf8               java/lang/Object
   #4 = Class              #3             // java/lang/Object
   #5 = Utf8               P0/I0
   #6 = Class              #5             // P0/I0
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = NameAndType        #7:#8          // "<init>":()V
  #10 = Methodref          #4.#9          // java/lang/Object."<init>":()V
  #11 = Utf8               test
  #12 = Utf8               ()Ljava/lang/Integer;
  #13 = Utf8               P0/Helper
  #14 = Class              #13            // P0/Helper
  #15 = Utf8               getC4
  #16 = Utf8               ()LP0/C4;
  #17 = NameAndType        #15:#16        // getC4:()LP0/C4;
  #18 = Methodref          #14.#17        // P0/Helper.getC4:()LP0/C4;
  #19 = Utf8               m
  #20 = NameAndType        #19:#12        // m:()Ljava/lang/Integer;
  #21 = InterfaceMethodref #6.#20         // P0/I0.m:()Ljava/lang/Integer;
  #22 = Utf8               main
  #23 = Utf8               ([Ljava/lang/String;)V
  #24 = NameAndType        #11:#12        // test:()Ljava/lang/Integer;
  #25 = Methodref          #2.#24         // P0/C3.test:()Ljava/lang/Integer;
  #26 = Utf8               java/lang/System
  #27 = Class              #26            // java/lang/System
  #28 = Utf8               out
  #29 = Utf8               Ljava/io/PrintStream;
  #30 = NameAndType        #28:#29        // out:Ljava/io/PrintStream;
  #31 = Fieldref           #27.#30        // java/lang/System.out:Ljava/io/PrintStream;
  #32 = Utf8               Return value:
  #33 = String             #32            // Return value:
  #34 = Utf8               java/io/PrintStream
  #35 = Class              #34            // java/io/PrintStream
  #36 = Utf8               print
  #37 = Utf8               (Ljava/lang/String;)V
  #38 = NameAndType        #36:#37        // print:(Ljava/lang/String;)V
  #39 = Methodref          #35.#38        // java/io/PrintStream.print:(Ljava/lang/String;)V
  #40 = Utf8               println
  #41 = Utf8               (Ljava/lang/Object;)V
  #42 = NameAndType        #40:#41        // println:(Ljava/lang/Object;)V
  #43 = Methodref          #35.#42        // java/io/PrintStream.println:(Ljava/lang/Object;)V
  #44 = Utf8               Code
{
  public P0.C3();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #10                 // Method java/lang/Object."<init>":()V
         4: return

  public static java.lang.Integer test();
    descriptor: ()Ljava/lang/Integer;
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=1, args_size=0
         0: invokestatic  #18                 // Method P0/Helper.getC4:()LP0/C4;
         3: astore_0
         4: aload_0
         5: invokeinterface #21,  1           // InterfaceMethod P0/I0.m:()Ljava/lang/Integer;
        10: areturn

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=2, args_size=1
         0: invokestatic  #25                 // Method test:()Ljava/lang/Integer;
         3: astore_1
         4: getstatic     #31                 // Field java/lang/System.out:Ljava/io/PrintStream;
         7: dup
         8: ldc           #33                 // String Return value:
        10: invokevirtual #39                 // Method java/io/PrintStream.print:(Ljava/lang/String;)V
        13: aload_1
        14: invokevirtual #43                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
        17: return
}

```

We observed that the call to `var0.m()` in `P0\C3.test()` uses `invokeinterface`. As a result, we reviewed the relevant sections on `invokeinterface` in both JVM SE8 and SE11.

In SE11, the [Method Selection](https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-5.html#jvms-5.4.6) rule states, "Otherwise, the maximally-specific superinterface methods of C are determined ([§5.4.3.3](https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-5.html#jvms-5.4.3.3)). If exactly one matches the method's name and descriptor and is not abstract, then it is the selected method." Therefore, the selected method should be `null` because two superinterface methods were found (`P0\I0.m()` and `P0\I5.m()`). According to the [SE11 `invokeinterface` specification](https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-6.html#jvms-6.5.invokeinterface), "Otherwise, if no method is selected, and there are multiple maximally-specific superinterface methods of C that match the resolved method's name and descriptor and are not abstract, `invokeinterface` throws an `IncompatibleClassChangeError`." Therefore, the expected behavior in Hotspot (11.0.26) should be an `IncompatibleClassChangeError` rather than an `AbstractMethodError`.
ACTUAL -
The output is as follows:

Hotspot(1.8.0_441):
```
Exception in thread "main" java.lang.IncompatibleClassChangeError: Conflicting default methods: P0/I0.m P0/I5.m
at P0.C4.m(Unknown Source)
at P0.C3.test(Unknown Source)
at P0.C3.main(Unknown Source)
```

Hotspot(11.0.26):
```
Exception in thread "main" java.lang.AbstractMethodError: Receiver class P0.C4 does not define or inherit an implementation of the resolved method 'java.lang.Integer m()' of interface P0.I0.
at P0.C3.test(Unknown Source)
at P0.C3.main(Unknown Source)
```

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

```java
package P0;

import java.io.PrintStream;

public class C3 implements I0 {
    public C3() {
    }

    public static Integer test() {
        C4 var0 = Helper.getC4();
        return var0.m();
    }

    public static void main(String[] var0) {
        Integer var1 = test();
        PrintStream var10000 = System.out;
        var10000.print("Return value: ");
        var10000.println(var1);
    }
}
```

```java
package P0;

public class C4 implements I0, I5 {
    public C4() {
    }
}
```

```java
package P0;

public interface I0 {
    default Integer m() {
        return new Integer(0);
    }
}
```

```java
package P0;

public interface I5 {
    default Integer m() {
        return new Integer(5);
    }
}
```
---------- END SOURCE ----------


Comments
A pull request was submitted for review. Branch: jdk25 URL: https://git.openjdk.org/jdk/pull/26304 Date: 2025-07-15 00:31:41 +0000
15-07-2025

Changeset: f36147b3 Branch: master Author: David Holmes <dholmes@openjdk.org> Date: 2025-07-14 22:53:45 +0000 URL: https://git.openjdk.org/jdk/commit/f36147b3263662229e9a0ec712b9748711d2d85d
14-07-2025

A pull request was submitted for review. Branch: master URL: https://git.openjdk.org/jdk/pull/26122 Date: 2025-07-03 22:11:55 +0000
03-07-2025

This problem is caused by JDK-8186092. Here is some itable and default-method logging for a working version of the test in JDK 9: [0.210s][debug][defaultmethods] Class P0.C4 requires default method processing [0.210s][debug][defaultmethods] P0/C4 [0.210s][debug][defaultmethods] java/lang/Object [0.210s][debug][defaultmethods] P0/I0 [0.210s][debug][defaultmethods] java/lang/Object [0.210s][debug][defaultmethods] P0/I5 [0.210s][debug][defaultmethods] java/lang/Object [0.210s][debug][defaultmethods] Slots that need filling: [0.210s][debug][defaultmethods] m()Ljava/lang/Integer; [0.210s][debug][defaultmethods] <clinit>()V [0.210s][debug][defaultmethods] registerNatives()V [0.210s][debug][defaultmethods] Looking for default methods for slot m()Ljava/lang/Integer; [0.210s][debug][defaultmethods] 'Conflicting default methods: P0/I0.m P0/I5.m' [0.210s][debug][defaultmethods] Looking for default methods for slot <clinit>()V [0.210s][debug][defaultmethods] Looking for default methods for slot registerNatives()V [0.210s][debug][defaultmethods] Creating defaults and overpasses... [0.210s][debug][defaultmethods] for slot: m()Ljava/lang/Integer; [0.210s][debug][defaultmethods] java/lang/IncompatibleClassChangeError: Conflicting default methods: P0/I0.m P0/I5.m [0.210s][debug][defaultmethods] Created 1 overpass methods [0.210s][debug][defaultmethods] Created 0 default methods [0.210s][debug][defaultmethods] Default method processing complete [0.210s][debug][vtables ] Initializing: P0/I0 [0.210s][trace][vtables ] copy vtable from java.lang.Object to P0.I0 size 5 [0.210s][debug][itables ] 320: Initializing itable indices for interface P0/I0 [0.210s][trace][itables ] itable index 0 for method: P0.I0.m()Ljava/lang/Integer;, flags: public default [0.210s][debug][vtables ] Initializing: P0/I5 [0.210s][trace][vtables ] copy vtable from java.lang.Object to P0.I5 size 5 [0.210s][debug][itables ] 321: Initializing itable indices for interface P0/I5 [0.210s][trace][itables ] itable index 0 for method: P0.I5.m()Ljava/lang/Integer;, flags: public default [0.210s][debug][vtables ] Initializing: P0/C4 [0.210s][trace][vtables ] copy vtable from java.lang.Object to P0.C4 size 6 [0.210s][trace][vtables ] adding P0.C4.m()Ljava/lang/Integer; at index 5, flags: public volatile synthetic overpass [0.210s][debug][itables ] 322: Initializing itables for P0/C4 [0.210s][trace][itables ] interface: P0.I0, ime_num: 0, target: P0.C4.m()Ljava/lang/Integer;, method_holder: P0.C4 target_method flags: public volatile synthetic overpass [0.210s][trace][itables ] interface: P0.I5, ime_num: 0, target: P0.C4.m()Ljava/lang/Integer;, method_holder: P0.C4 target_method flags: public volatile synthetic overpass Note in the last two lines we create itable entries for both I0.m and I5.m and direct them to C4.m which is an overpass to throw: java/lang/IncompatibleClassChangeError: Conflicting default methods: P0/I0.m P0/I5.m Now here is the logging for JDK 10: [0.171s][debug][defaultmethods] Class P0.C4 requires default method processing [0.171s][debug][defaultmethods] P0/C4 [0.171s][debug][defaultmethods] java/lang/Object [0.171s][debug][defaultmethods] P0/I0 [0.171s][debug][defaultmethods] java/lang/Object [0.171s][debug][defaultmethods] P0/I5 [0.171s][debug][defaultmethods] java/lang/Object [0.171s][debug][defaultmethods] Slots that need filling: [0.171s][debug][defaultmethods] m()Ljava/lang/Integer; [0.171s][debug][defaultmethods] <clinit>()V [0.171s][debug][defaultmethods] registerNatives()V [0.171s][debug][defaultmethods] Looking for default methods for slot m()Ljava/lang/Integer; [0.171s][debug][defaultmethods] 'Conflicting default methods: P0/I0.m P0/I5.m' [0.171s][debug][defaultmethods] Looking for default methods for slot <clinit>()V [0.171s][debug][defaultmethods] Looking for default methods for slot registerNatives()V [0.171s][debug][defaultmethods] Creating defaults and overpasses... [0.171s][debug][defaultmethods] for slot: m()Ljava/lang/Integer; [0.171s][debug][defaultmethods] java/lang/IncompatibleClassChangeError: Conflicting default methods: P0/I0.m P0/I5.m [0.171s][debug][defaultmethods] Created 1 overpass methods [0.171s][debug][defaultmethods] Created 0 default methods [0.171s][debug][defaultmethods] Default method processing complete [0.171s][debug][vtables ] Initializing: P0/I0 [0.171s][trace][vtables ] copy vtable from java.lang.Object to P0.I0 size 5 [0.171s][debug][itables ] 310: Initializing itable indices for interface P0/I0 [0.171s][trace][itables ] itable index 0 for method: P0.I0.m()Ljava/lang/Integer;, flags: public default [0.171s][debug][vtables ] Initializing: P0/I5 [0.171s][trace][vtables ] copy vtable from java.lang.Object to P0.I5 size 5 [0.171s][debug][itables ] 311: Initializing itable indices for interface P0/I5 [0.171s][trace][itables ] itable index 0 for method: P0.I5.m()Ljava/lang/Integer;, flags: public default [0.171s][debug][vtables ] Initializing: P0/C4 [0.171s][trace][vtables ] copy vtable from java.lang.Object to P0.C4 size 6 [0.171s][trace][vtables ] adding P0.C4.m()Ljava/lang/Integer; at index 5, flags: public volatile synthetic overpass [0.171s][debug][itables ] 312: Initializing itables for P0/C4 And that is the end of it - there are no itable entries created for the interface methods. These slots then get filled with an overpass method to throw AbstractMethodError because it appears the interface method does not exist - which is not the case. Hence the test throws AbstractMethodError. If the test is changed to use invokevirtual instead then we execute C4.m() which is the overpass to throw the ICCE. JDK-8186092 was trying to address a problem whereby if I0 and I5 were loaded by different classloaders a loader constraint violation was generated. To quote from JDK-8186092: > the proposed rule is that preparation should simulate method selection and generate loader constraints iff selection would succeed. as selection here cannot succeed then the loader constraint violation should not have been reported. Part of the fix for JDK-8186092 was to simply skip overpass methods when populating the itable for the class - hence what we observed in the logging and hence why the test fails after that change. To again quote from JDK-8186092: > There is a bug in the implementation. When the preparation finds the JVM internal representation for a default method error, i.e. an overpass, and that overrides another method - we should NOT perform the loader constraint check. Yet as far as I can see the code change skipped all overpass methods, not just those that "override another method" (though in this case C4.m would be an override for I0.m and I5.m). We can't simply not-skip all overpass methods (which fixes the current test case) as that then causes part of the regression test for JDK-8186092 to fail. I actually touched on this in the code review for JDK-8186092 https://mail.openjdk.org/pipermail/hotspot-runtime-dev/2017-September/024642.html but didn't quite see this problem.
01-07-2025

Using the supplied generator program I find that the AbstractMethodError started appearing with JDK 10. There must be a difference (likely access modifier) in the generated code versus the distilled test logic. But so far I have been unable to locate it. Update: the difference is in the invocation line. The distilled test uses invokevirtual whilst the failing generated test uses invokeinterface. The distilled test has: 24: invokevirtual #9 // Method P0/C4.m:()Ljava/lang/Integer; as it is invoking C4.m not I0.m or I5.m. In contrast the generated class has: 5: invokeinterface #21, 1 // InterfaceMethod P0/I0.m:()Ljava/lang/Integer; If I change the distilled test to cast the C4 instance to I0 instance, then I get: 24: invokeinterface #9, 1 // InterfaceMethod P0/I0.m:()Ljava/lang/Integer; and when the test runs in Java 10: Exception in thread "main" java.lang.AbstractMethodError at Test.main(Test.java:5) In Java11 we get the more informative (but incorrect): Exception in thread "main" java.lang.AbstractMethodError: Receiver class P0.C4 does not define or inherit an implementation of the resolved method m()Ljava/lang/Integer; of interface P0.I0. at Test.main(Test.java:5)
16-05-2025

> In SE11, the [Method Selection](https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-5.html#jvms-5.4.6)&#xA0;rule states, > "Otherwise, the maximally-specific superinterface methods of C are determined ([§5.4.3.3](https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-5.html#jvms-5.4.3.3)). > If exactly one matches the method's name and descriptor and is not abstract, then it is the selected method." > Therefore, the selected method should be `null` because two superinterface methods were found (`P0\I0.m()` and `P0\I5.m()`). Section 5.4.3.3 states: 3. Otherwise, method resolution attempts to locate the referenced method in the superinterfaces of the specified class C: • If the maximally-specific superinterface methods of C for the name and descriptor specified by the method reference include exactly one method that does not have its ACC_ABSTRACT flag set, then this method is chosen and method lookup succeeds. • Otherwise, if any superinterface of C declares a method with the name and descriptor specified by the method reference that has neither its ACC_PRIVATE flag nor its ACC_STATIC flag set, one of these is arbitrarily chosen and method lookup succeeds. --- So we chose either I0.m or I5.m arbitrarily as the resolved method. Section 5.4.6 then describes how an actual method is selected, and here the maximally-specific rules again come into play but this time if there is not a single method (there isn't) then selection implicitly fails. So then we move onto the runtime exceptions specification for invokeinterface which states: - Otherwise, if no method is selected, and there are multiple maximally-specific superinterface methods of C that match the resolved method's name and descriptor and are not abstract, invokeinterface throws an IncompatibleClassChangeError - Otherwise, if no method is selected, and there are no maximally-specific superinterface methods of C that match the resolved method's name and descriptor and are not abstract, invokeinterface throws an AbstractMethodError. So we should in fact be throwing the ICCE.
16-05-2025

I distilled the test case down to: public class C4 implements I0, I5 { public C4() { } } public interface I0 { default Integer m() { return new Integer(0); } } public interface I5 { default Integer m() { return new Integer(5); } } public class Test { public static void main(String[] args) { C4 c = new C4(); System.out.println("Result: " + c.m()); } } where initially we change one of the m() methods to something else so it will compile, then separately compile it with the name restored to m(). In all Java version from 8 through to 24 (all GA versions) and a latest JDK 25 build, I got: Exception in thread "main" java.lang.IncompatibleClassChangeError: Conflicting default methods: I0.m I5.m at C4.m(C4.java) at Test.main(Test.java:4) I tried adding back the packages (though it should make no difference) and still got the same result.
16-05-2025