Multiple Inheritance via Interfaces
Java forbids inheriting state from more than one class, but a class can implement any number of interfaces — and modern default methods make that almost as powerful as multiple inheritance, minus the mess.
"Multiple inheritance" means a type can have more than one supertype at once. Java deliberately supports this only for interfaces, never for classes. Understanding why — and how default-method conflicts get resolved when they do occur — is essential to using interfaces well.
Why Java Disallows Multiple Class Inheritance
A class in Java may extend exactly one superclass. Languages that allow a class to extend two or more classes run into the classic "diamond problem": if classes B and C both extend A and override a method, and D extends both B and C, which version does D inherit? The ambiguity gets worse when B and C also carry their own instance fields — there is no principled way to merge two independent sets of inherited state.
Java's designers avoided this entirely by capping class inheritance at one direct superclass. Every class ultimately has a single, unambiguous chain of ancestors up to Object, so field layout, constructor chaining, and method dispatch stay simple and predictable.
Interfaces: Multiple Inheritance of Type
While a class can extend only one class, it can implement any number of interfaces. This gives Java a controlled form of multiple inheritance — multiple inheritance of type (a class can satisfy many "is-a" contracts) and, since Java 8, multiple inheritance of default behavior. It does not give multiple inheritance of state: interfaces cannot declare instance fields, so there is no diamond-of-data problem to solve.
Duck satisfies two independent contracts at once — something impossible with single class inheritance alone.
interface Flyable {
void fly();
}
interface Swimmable {
void swim();
}
// A single class can implement as many interfaces as it needs.
class Duck implements Flyable, Swimmable {
@Override public void fly() { System.out.println("Duck flies"); }
@Override public void swim() { System.out.println("Duck swims"); }
}Basic Syntax for Implementing Several Interfaces
List the interfaces after implements, separated by commas. A class may simultaneously extend one superclass and implement several interfaces.
class Vehicle { }
interface Drivable { void drive(); }
interface Refuelable { void refuel(); }
class Car extends Vehicle implements Drivable, Refuelable {
@Override public void drive() { System.out.println("Driving"); }
@Override public void refuel() { System.out.println("Refueling"); }
}The Diamond Problem Returns — For Default Methods
Java 8 added default methods, which let an interface provide a method body. This reintroduces a diamond scenario, but only for behavior, not state: if a class implements two interfaces that each declare a default method with the same signature, the compiler cannot silently pick one — that would be an arbitrary, hidden decision.
interface Left {
default String describe() { return "Left"; }
}
interface Right {
default String describe() { return "Right"; }
}
// Compile error: Both interfaces provide describe(); the
// class must resolve the conflict explicitly.
class Both implements Left, Right {
// error: class Both inherits unrelated defaults for describe() from Left and Right
}Conflict Resolution Rules
Java resolves potential ambiguity with a small set of precedence rules, applied in order:
1. Classes win over interfaces. A method inherited from a superclass (or declared in the class itself) always takes priority over any default method from an interface, even if the interface method has the same signature.
2. More specific interfaces win. If interface B extends interface A and overrides A's default method, and a class implements both A and B, B's version is used — it is considered more specific.
3. Otherwise, it's an error. If two unrelated interfaces both supply a default for the same signature and neither is more specific, the implementing class must override the method itself to resolve the ambiguity. This is a compile-time error, not a runtime surprise — Java refuses to guess.
Resolving a Conflict: InterfaceName.super.method()
Inside the overriding method, you can explicitly select one interface's default implementation using InterfaceName.super.methodName(), and even combine both.
InterfaceName.super.method() is the only way to invoke a specific interface's default method from within an overriding implementation; you cannot call it from outside the class.
interface Left {
default String describe() { return "Left"; }
}
interface Right {
default String describe() { return "Right"; }
}
class Both implements Left, Right {
@Override
public String describe() {
// Explicitly pick, or combine, the conflicting defaults.
return Left.super.describe() + " & " + Right.super.describe();
}
}
class Demo {
public static void main(String[] args) {
System.out.println(new Both().describe()); // Left & Right
}
}Static and Private Interface Methods Don't Conflict
Interface static methods belong to the interface itself and are never inherited by implementing classes — you must call them as InterfaceName.staticMethod(). Because they are not inherited, two interfaces can each declare a static method with the same name without any conflict for an implementing class. Private interface methods (Java 9+) are helpers used only inside the interface's own default/static methods and are likewise invisible to implementers, so they cannot conflict either.
interface A {
static void helper() { System.out.println("A.helper"); }
}
interface B {
static void helper() { System.out.println("B.helper"); }
}
class C implements A, B { }
// No conflict: static methods are not inherited.
// Call them explicitly: A.helper(); B.helper();Interface Constants and Multiple Implementation
Fields declared in an interface are implicitly public static final — constants, not per-instance state. If two implemented interfaces declare a constant with the same name, referring to the bare name inside the implementing class is ambiguous and must be qualified as InterfaceName.CONSTANT. This is a much milder issue than a method conflict since it's caught immediately at the point of use, not at the class declaration.
Diamond Inheritance of Interfaces Themselves
Interfaces can extend other interfaces, including multiple interfaces at once (interface C extends A, B is legal, unlike classes). If a class ultimately implements the same interface through two different paths, that is not a conflict at all — there is only one abstract contract being satisfied, and the JVM does not care how many paths led to it.
interface Animal { default String sound() { return "..."; } }
interface Pet extends Animal { }
interface Wild extends Animal { }
// Dog reaches Animal.sound() through two paths — still just one method.
class Dog implements Pet, Wild { }Practical Usage: Mixins and Capability Interfaces
Multiple interface implementation is Java's mechanism for mixin-style design: small, focused interfaces such as Comparable, Serializable, Iterable, or a custom Auditable can each be layered onto a class independently. A class opts into exactly the capabilities it needs without inheriting unrelated implementation state, keeping designs composable rather than forcing everything into one rigid class hierarchy.
interface Loggable {
default void log(String msg) {
System.out.println("[LOG] " + msg);
}
}
interface Cacheable {
default void cache() {
System.out.println("caching...");
}
}
class ReportGenerator implements Loggable, Cacheable {
void generate() {
log("starting report");
cache();
}
}Edge Cases
An abstract method with the same signature in two interfaces (neither providing a default) is not a conflict — the implementing class simply provides one implementation that satisfies both contracts.
If one interface provides a default and another declares the same signature as abstract only, there is no ambiguity: the abstract declaration does not compete, and some compilers still require the class to override depending on the exact relationship, so it is good practice to always check compiler diagnostics rather than assume.
A default method conflict is a compile-time error, never a runtime one — Java will not let a class with an unresolved diamond conflict compile at all, so this class of bug cannot reach production.
Best Practices
Keep default methods small and rare; use interfaces primarily to describe contracts, not to smuggle in implementation inheritance. Favor composition (an object holding a reference to a helper) over deep interface hierarchies with many overlapping defaults. When a genuine conflict does arise, resolve it explicitly with InterfaceName.super rather than deleting one interface — that preserves the intent of implementing both.
Complete Example
interface Employee {
default double bonus() { return 100.0; }
}
interface Manager {
default double bonus() { return 500.0; }
}
class TeamLead implements Employee, Manager {
@Override
public double bonus() {
// Explicitly combine both roles' bonuses.
return Employee.super.bonus() + Manager.super.bonus();
}
}
public class Main {
public static void main(String[] args) {
TeamLead lead = new TeamLead();
System.out.println(lead.bonus()); // 600.0
}
}Mental Model & Quick Reference
Think of it as: "one parent for state, many contracts for behavior." A class inherits fields and a single implementation lineage from at most one superclass, but can promise to fulfill any number of interface contracts, and — since Java 8 — inherit any number of conflict-free default implementations too.
class extends 1 class -> no ambiguity possible for state
class implements N interfaces -> allowed, no state conflict
two defaults, same signature -> compile error unless overridden
class method vs interface default -> class always wins
InterfaceName.super.method() -> explicitly picks one default
static/private interface methods -> never inherited, never conflictKey Takeaways
Java allows a class to extend only one superclass, but implement any number of interfaces.
Interfaces provide multiple inheritance of type and, via default methods, of conflict-free behavior — never of instance state.
Two default methods with the same signature from unrelated interfaces cause a compile error unless the class overrides the method.
InterfaceName.super.method() lets an override explicitly select or combine a specific interface's default implementation.
A class's own (or inherited-from-superclass) method always wins over any interface default with the same signature.
Static and private interface methods are never inherited, so they never participate in diamond conflicts.
interface Left { default String name() { return "L"; } } interface Right { default String name() { return "R"; } } class Both implements Left, Right { } // compile error class Both implements Left, Right { @Override public String name() { return Left.super.name(); // or Right.super.name(), or combine both } }
Two conflicting default methods must be resolved explicitly by overriding the method in the implementing class; Java will never silently pick one for you.
class Manager extends Employee, Auditor { } // does not compile class Manager extends Employee implements Auditor { }
Java classes can extend only one class. Additional supertypes must be interfaces, brought in via implements.
interface Greeter { static void hello() { System.out.println("hi"); } } class Person implements Greeter { void run() { hello(); } // does not compile } class Person implements Greeter { void run() { Greeter.hello(); } }
Static interface methods are not inherited by implementing classes; they must always be called through the interface name.
// Assuming a diamond of default methods is a runtime issue // and testing to "see what happens" in production.
// Treat it as what it is: a compile-time error. // Fix it during development by overriding the ambiguous method.
Unresolved default-method diamonds never compile, so this category of bug cannot surface at runtime — don't design defensive runtime checks for it.
Why can a Java class extend only one superclass but implement many interfaces?
Extending multiple classes could create ambiguous inherited state and method dispatch (the diamond problem for fields and overridden methods). Interfaces carry no instance state, so implementing several introduces no state ambiguity; only default-method behavior can conflict, and Java requires that conflict to be resolved explicitly.
Two interfaces implemented by the same class each declare an abstract (non-default) method with an identical signature. Does this cause a compile error?
No. With no default implementations involved, there's nothing to disambiguate — the implementing class simply provides one method body that satisfies both interfaces' contracts.
What happens if a class extends a superclass that has a method foo(), and also implements an interface with a default foo() method?
The class (or superclass) method always wins; the interface default is not used, and no conflict is reported, because classes take precedence over interface defaults.
Write the syntax to call interface Right's default implementation of describe() from inside an overriding method in a class implementing both Left and Right.
Can a private interface method (Java 9+) cause a diamond conflict for an implementing class?
No. Private interface methods are only callable from within the interface's own default or static methods; they are never inherited or visible to implementing classes, so they cannot conflict.
Is a default-method diamond conflict a compile-time or a runtime problem?
Always compile-time. Java refuses to compile a class with an unresolved ambiguity between inherited default methods.
Interface design question: why do frameworks favor many small interfaces (like Comparable, Serializable, Iterable) over one large interface?
Small, focused interfaces let a class opt into exactly the capabilities it needs (a mixin style), minimizing the surface area for default-method conflicts and keeping each contract easy to understand and implement correctly.
Write two interfaces, Walker and Swimmer, each with a default method move() that prints a different message. Write a class Amphibian implementing both that calls both defaults in its own move() override.
interface Walker { default void move() { System.out.println("Walking"); } } interface Swimmer { default void move() { System.out.println("Swimming"); } } class Amphibian implements Walker, Swimmer { @Override public void move() { Walker.super.move(); Swimmer.super.move(); } } public class Main { public static void main(String[] args) { new Amphibian().move(); } }
Prints: Walking Swimming The override resolves the diamond by explicitly invoking both interfaces' default implementations via InterfaceName.super.
What does this print, and why isn't it a compile error?
Prints 2. Since B extends A and overrides val(), B is considered more specific. Java's conflict rules pick the most specific interface's default automatically, so no ambiguity — and no error — arises.
Write a class that fails to compile due to a default-method diamond conflict between two unrelated interfaces, then fix it.
interface Payer { default String pay() { return "cash"; } } interface Receiver { default String pay() { return "check"; } } // Fails to compile as-is: // class Cashier implements Payer, Receiver { } class Cashier implements Payer, Receiver { @Override public String pay() { return Payer.super.pay(); // explicitly resolved } }
The unresolved version fails because Payer and Receiver each supply an unrelated default for pay(). Overriding pay() in Cashier and choosing (or combining) an interface via InterfaceName.super fixes the ambiguity.
Would new Box().unit() compile? What does this program actually print?
new Box().unit() would NOT compile — static interface methods are never inherited, so Box has no unit() member accessible via an instance. The program as written prints 1.0, since Shape.unit() is called directly on the interface, which is always legal regardless of Sized also declaring a same-named static method (no conflict exists because neither is inherited).
interface A { default int val() { return 1; } }
interface B extends A { default int val() { return 2; } }
class C implements A, B { }
public class Main {
public static void main(String[] args) {
System.out.println(new C().val());
}
}interface Shape {
static double unit() { return 1.0; }
}
interface Sized {
static double unit() { return 100.0; }
}
class Box implements Shape, Sized { }
public class Main {
public static void main(String[] args) {
System.out.println(Shape.unit());
}
}