Multiple inheritance in Java means that one type gets members from more than one parent type. A Java class can extend only one class, but it can implement any number of interfaces, and since Java 8 those interfaces can carry method bodies in the form of default methods.
We use multiple inheritance to give one class several roles without copying code, for example a smart bulb that can be switched on and dimmed, or a record that is both Comparable and part of our own domain interface.
The following example gives a SmartBulb class behavior from two interfaces at the same time.
interface Switchable {
default String powerOn() {
return "on";
}
}
interface Dimmable {
default int level() {
return 100;
}
}
class SmartBulb implements Switchable, Dimmable {
}
SmartBulb bulb = new SmartBulb();
String power = bulb.powerOn(); // "on"
int brightness = bulb.level(); // 100
boolean twoTypes = bulb instanceof Dimmable; // true
The class SmartBulb declares no methods at all, yet it has both powerOn() and level(). We first look at which kinds of multiple inheritance Java allows, at the diamond problem and the three rules the compiler applies when two parents define the same method, and finally at composition as the alternative for sharing state.
1. What Java Allows and What It Does Not
Multiple inheritance comes in three kinds, and Java treats each one differently. The difference comes from fields. Fields hold state, and two parent classes with fields of the same name would leave the child with two copies and no clear answer about which one a method changes.

| Kind | Meaning | Supported in Java |
|---|---|---|
| Multiple inheritance of state | Fields come from more than one parent | No, a class has only one direct superclass |
| Multiple inheritance of implementation | Method bodies come from more than one parent | Yes since Java 8, through default methods in interfaces |
| Multiple inheritance of type | An object is an instance of several types | Yes since Java 1.0, through implements A, B |
Interfaces cannot declare instance fields, only constants, so implementing many interfaces never brings two copies of the same state into one object. Extending two classes is a compile error.
// does not compile: Java allows only one class after extends
class Hub extends Router, Modem {
}
When we need code from two classes, we either move the shared behavior into interfaces with default methods, or we keep instances of both classes as fields and delegate to them, as shown in section 5.
2. Default Methods Add Behavior to Interfaces
A default method is an interface method with a body, marked with the default keyword. Every implementing class inherits the body and can override it. Java 8 added default methods so that the JDK could add methods such as Iterable.forEach() and Collection.stream() to existing interfaces without breaking every class that already implemented them. The default methods guide covers the syntax in detail.
Say a home automation library ships a Schedulable interface, and hundreds of apps have device classes that implement it. The library team wants every device to print a short summary of its schedule. If they add an abstract method, every app stops compiling until it writes the method. With a default method, old device classes keep compiling and get the new summary() method without any change.
interface Schedulable {
String nextRun();
default String summary() {
return "next run at " + nextRun();
}
}
class GardenSprinkler implements Schedulable {
public String nextRun() {
return "06:00";
}
}
GardenSprinkler sprinkler = new GardenSprinkler();
String plan = sprinkler.summary(); // "next run at 06:00"
We can see that the default method calls the abstract method nextRun(), so each class still supplies its own data while the interface supplies the shared logic. A class that implements several such interfaces collects several default methods, which is multiple inheritance of implementation.
3. The Diamond Problem and Java’s Three Rules
The diamond problem appears when a class inherits the same method from two parents that both got it from a common ancestor. Drawn as a class diagram, the four types form a diamond. In C++ with multiple class inheritance, the question is which copy of the method and which copy of the fields the child gets. Java has no fields in interfaces, so only the method question remains, and the compiler answers it with the rules from JLS 8.4.8 and JLS 9.4.1.3.
All three rules use the same device interfaces. The Device interface has a default describe() method, and two subinterfaces override it.
interface Device {
default String describe() {
return "device";
}
}
interface Light extends Device {
@Override
default String describe() {
return "light";
}
}
interface Speaker extends Device {
@Override
default String describe() {
return "speaker";
}
}
3.1. A Class Method Wins Over Any Default Method
If a superclass declares the method, that version wins over every default method from the interfaces. A default method only fills a gap when no class in the hierarchy provides the method. The superclass method must be public, because interface methods are public and an inherited method cannot reduce the access.
class PoweredDevice {
public String describe() {
return "powered device";
}
}
class WallSocket extends PoweredDevice implements Light {
}
String socket = new WallSocket().describe(); // "powered device"
3.2. The More Specific Interface Wins
If two interfaces provide the method and one interface extends the other, the subinterface wins. The Light interface already overrides Device.describe(), so the compiler ignores the version in Device even when a class lists both interfaces.
class CeilingLight implements Device, Light {
}
String ceiling = new CeilingLight().describe(); // "light"
3.3. Otherwise the Class Must Override the Method
When two unrelated interfaces both provide a default method with the same signature, neither rule applies, and the class does not compile. The compiler does not guess.
// does not compile: types Light and Speaker are incompatible, SmartLamp inherits unrelated defaults for describe()
class SmartLamp implements Light, Speaker {
}
To fix the conflict, the class overrides the method, and inside the override it can call a specific parent with the InterfaceName.super.method() form. The class can pick one parent or combine both.
class SmartLamp implements Light, Speaker {
@Override
public String describe() {
return Light.super.describe() + " + " + Speaker.super.describe();
}
}
String lamp = new SmartLamp().describe(); // "light + speaker"
The call Light.super.describe() is legal only inside a class or interface that lists Light as a direct parent. Code that uses a SmartLamp object from outside calls lamp.describe() and gets the version the class chose.
4. Conflicts the Compiler Still Rejects
A few more combinations look harmless but fail to compile. Most of them have a short fix.
- An abstract method in one interface and a default method with the same signature in another interface. The class inherits both and has to override the method, even though one parent has a body.
- Two default methods with the same name and parameters but incompatible return types, such as int and String. No override can satisfy both, so the class cannot implement the two interfaces together.
- A static interface method called through the implementing class. Static interface methods are not inherited, so we call them through the interface name.
- A superclass method that is not public and has the same signature as an interface method. The class inherits a method with weaker access and fails to compile.
// does not compile: TempSensor is not abstract and does not override abstract method id() in Named
interface Named {
String id();
}
interface Tagged {
default String id() {
return "tag";
}
}
class TempSensor implements Named, Tagged {
}
interface Firmware {
static String version() {
return "2.1";
}
}
String firmware = Firmware.version(); // "2.1"
Two abstract methods with the same signature are not a conflict. One implementation in the class satisfies both interfaces, which is common when two libraries define the same callback.
5. Composition Instead of Two Superclasses
Default methods cannot hold state, so they do not help when both parents need fields. For that case we keep an instance of each class as a field and forward calls to it. This technique is called composition, or delegation, and it is the reason the composition relationship appears in most design advice.
A smart plug that measures energy and also follows a timer schedule is a typical case. The energy meter and the scheduler already exist as classes with their own fields, and the plug reuses both without extending either.
class EnergyMeter {
int wattHours(int hours) {
return hours * 60;
}
}
class Timer {
String nextSwitch() {
return "22:00";
}
}
class SmartPlug {
private final EnergyMeter meter = new EnergyMeter();
private final Timer timer = new Timer();
int usage(int hours) {
return meter.wattHours(hours);
}
String nextSwitch() {
return timer.nextSwitch();
}
}
SmartPlug plug = new SmartPlug();
int used = plug.usage(5); // 300
String next = plug.nextSwitch(); // "22:00"
Composition costs a few forwarding methods, but each part stays testable on its own, and we can replace the Timer with another implementation without touching the class hierarchy.
6. Records and Sealed Interfaces With Several Parents
Records cannot extend a class, because every record already extends java.lang.Record. They can implement as many interfaces as they need, so interfaces with default methods are the main way to share behavior between records. A sealed interface adds a closed list of implementations, which lets a switch check every case at compile time.
sealed interface Gadget permits Bulb, Plug {
}
record Bulb(String room) implements Gadget, Switchable {
}
record Plug(String room, int watts) implements Gadget, Switchable {
}
static String label(Gadget gadget) {
return switch (gadget) {
case Bulb b -> b.room() + " bulb is " + b.powerOn();
case Plug p -> p.room() + " plug draws " + p.watts() + " W";
};
}
String kitchen = label(new Bulb("kitchen")); // "kitchen bulb is on"
String desk = label(new Plug("desk", 40)); // "desk plug draws 40 W"
Both records inherit powerOn() from Switchable and belong to the sealed Gadget type at the same time, so a Bulb object is also a Switchable and a Gadget.
7. Multiple Inheritance in Java FAQs
Interview questions about multiple inheritance mostly test the difference between classes and interfaces.
7.1. Does Java support multiple inheritance?
Yes for types and method bodies, no for state. A class can implement many interfaces and inherit their default methods, but it can extend only one class, so fields always come from a single chain of superclasses.
7.2. Why does Java not allow a class to extend two classes?
The designers left it out to avoid multiple inheritance of state. Two superclasses with fields would raise questions about which constructor runs first, and which copy of a field a method reads or writes. Interfaces have no instance fields, so the language allows any number of them.
7.3. Can an interface extend multiple interfaces?
Yes. An interface uses extends with a comma-separated list, for example interface SmartSpeaker extends Speaker, Schedulable. The same three rules apply when the parent interfaces declare the same default method.
7.4. Can we call a specific interface’s default method from outside the class?
No. The form Light.super.describe() works only inside a type that lists Light as a direct parent. From outside, we call the method on the object, and the class decides which version runs.
7.5. What if two interfaces declare the same abstract method?
Nothing breaks. The class writes one method, and that method implements both declarations, as long as the return types are compatible.
8. Conclusion
Java supports multiple inheritance of type through interfaces and, since Java 8, multiple inheritance of implementation through default methods. It never supports multiple inheritance of state, because a class has a single direct superclass.
When parents define the same method, a class method beats any default method, a more specific interface beats its parent, and in every other case the class must override the method and can delegate with X.super.method(). For shared state, composition with forwarding methods is the tool, and it works the same way for classes and records. The Java OOP concepts overview shows where multiple inheritance fits next to the other OOP principles, and the Java inheritance guide covers single inheritance with extends.
9. References
- Java Tutorials, Multiple Inheritance of State, Implementation, and Type
- Java Tutorials, Default Methods
- JLS 25, Inheritance, Overriding, and Hiding
- JLS 25, Inheriting Methods with Override-Equivalent Signatures
- JLS 25, Method Invocation Expressions
Happy Learning !!
Even this code (note how I took the calls out of the static method by adding a Test class) still fails to pass the IDE’s error checking and you get an error on the Animal class with something like:
Duplicate default methods named run with the parameters () and () are inherited from the types Crawlable and Moveable
package com.foo.bar; interface Moveable { default void run(){ System.out.println("I am running, kid !!"); } } interface Crawlable { default void run(){ System.out.println("I am running, daddy !!"); } } public class Animal implements Moveable, Crawlable { public void move() { Moveable.super.run(); Crawlable.super.run(); } } public class Test { public static void main(String[] args) { Animal animal = new Animal(); animal.move(); //What will happen when below statement will execute //self.run(); } }Yeh, it’s problem that
class.super.method()solve.Maybe I misunderstand, but class.super.method() does not solve the problem in my example above. I still get errors and I’m not in a static context…
It’s a cool idea and I’d like to understand. To help me, please post some code that will not generate errors either in the IDE or at compile time? Thanks!
interface Moveable { default void run(){ System.out.println("I am running, kid !!"); } } interface Crawlable { default void run(){ System.out.println("I am running, daddy !!"); } } public class Animal implements Moveable, Crawlable { public static void main(String[] args) { Animal self = new Animal(); self.run(); } public void run() { Moveable.super.run(); Crawlable.super.run(); } }Many thanks! I see my error now – not having a method named “run()” to match the interface.
The error message generated by that mistake was not helpful. It referenced “duplicate default methods” when in fact, the problem was the lack of a run() method in my Animal class…
Great example – thanks for posting!
This sounded great until I tried to put it into an IDE. Unless I made a mistake, the following code will not compile but will instead produce the error: “Cannot use super in a static context”
package com.foo.bar; interface Moveable { default void run(){ System.out.println("I am running, kid !!"); } } interface Crawlable { default void run(){ System.out.println("I am running, daddy !!"); } } public class Animal implements Moveable, Crawlable { public static void main(String[] args) { Animal self = new Animal(); Moveable.super.run(); Crawlable.super.run(); } }Please let me know if I got it wrong…
You are right. My point is to use
Moveable.super.run()style method calls to avoid conflict. You can definitely use it outside static methods. Or you can test by removing static keyword from main mathod.It goes even further…
Peter Verhas showed that you could use the default methods to induce attributes:
https://javax0.wordpress.com/2014/04/02/how-not-to-use-java-8-default-methods/
I tried to adopt his idea for observability, but it sees somehow “wrong”
https://codereview.stackexchange.com/questions/102596/observe-closeable-with-default-methods
Sorry, I am making a comment without knowing complete context. Have you considered Closeable and Observable separate; rather than try to combine them?