Back to Blog
Java

Java Interface Inheritance: Extending Contracts

Understand Java interface inheritance: extending interfaces, multiple inheritance, default method conflicts, and practical design considerations.

Java interfacesdefault methodsmultiple inheritancecontract designinheritance rules
Diagram showing a child interface extending two parent interfaces with default methods.

When you define an interface that extends another interface, you are creating a contract that inherits all the abstract methods, constants, and nested types from the parent. Java interface inheritance is a core mechanism for building layered APIs. For example, a ReadableRepository interface might extend a base Repository interface to add read-specific methods while preserving the common contract.

What an Interface Inherits When It Extends Another Interface

An interface can extend one or more other interfaces using the extends keyword. The child interface inherits every abstract method, constant field, and nested type declared in its parents. It also inherits default methods. Static methods in interfaces are not inherited, so they must be accessed through the interface that declares them.

public interface Repository { void connect(); void disconnect(); } public interface ReadableRepository extends Repository { String read(String id); }

Here, ReadableRepository now has three abstract methods: connect(), disconnect(), and read(String). Any non-abstract class that implements ReadableRepository must provide implementations for all three. This is the simplest form of interface inheritance: it composes contracts without adding any implementation behavior.

Extending Multiple Interfaces and the Diamond Problem

Java interfaces support multiple inheritance. A child interface can extend several parent interfaces, as in interface Combined extends A, B. This allows a contract to be composed from smaller, focused contracts. The diamond problem arises when two parent interfaces contribute methods with the same signature, especially when both provide default implementations.

public interface A { default void log() { System.out.println("A"); } } public interface B { default void log() { System.out.println("B"); } } public interface C extends A, B { // Must override log() to resolve the conflict @Override default void log() { A.super.log(); // or provide a new implementation } }

If both parent interfaces declare an abstract method with the same signature, there is no conflict because the child inherits a single abstract method. The conflict that requires intervention occurs only when the parents provide different default implementations. In that case, the child must override the method to disambiguate, or the code will not compile.

Default Methods and Inheritance Rules

Default methods were introduced in Java 8 to allow interfaces to evolve without breaking existing implementations. When a child interface extends a parent that has a default method, the child inherits that default method. The child can override it, either by providing a new implementation or by calling the parent's version with Parent.super.method().

public interface Base { default String name() { return "Base"; } } public interface Child extends Base { @Override default String name() { return "Child"; } }

If a class implements a child interface without overriding an inherited default method, the class receives the default behavior. If a class implements multiple interfaces with conflicting default methods, the class must override the method explicitly.

How Class Implementation Interacts with Interface Inheritance

When a non-abstract class implements an interface, it must implement every abstract method in that interface and in all its parent interfaces. This is a common source of compile-time errors: developers often forget that a child interface brings in methods from its parents.

public class SimpleRepository implements ReadableRepository { @Override public void connect() { } @Override public void disconnect() { } @Override public String read(String id) { return null; } }

If a class implements multiple interfaces that share a method signature, it must provide a single implementation that satisfies both contracts. This is similar to the diamond problem but at the class level. The class's method implementation is used regardless of which interface is the declared type.

Common Pitfalls and Compile-Time Errors

The most frequent mistake is omitting an inherited abstract method. The compiler error Class is not abstract and does not override abstract method points to the missing method. Another pitfall is trying to call a static method from an inherited interface. Static interface methods are not inherited; you must qualify the call with the interface name.

public interface Utility { static void print() { System.out.println("Utility"); } } public interface SubUtility extends Utility { // Static method print() is not inherited here. } // Calling Utility.print() is valid; SubUtility.print() is not.

Default method conflicts are another compile-time issue. If a child interface extends two parents that both define the same default method, the child must override it. The same rule applies to a class implementing two interfaces with conflicting default methods. The compiler forces you to resolve the ambiguity, which is a safety feature rather than a limitation.

Maintainability and Design Considerations

Interface inheritance is a powerful tool for defining contracts, but it can lead to fragile designs when overused. Deep inheritance hierarchies make it hard to understand what a class actually must implement. A better approach is often to compose small, focused interfaces and let classes implement several of them. This follows the interface segregation principle and keeps contracts readable.

Consider whether a child interface truly needs to inherit from a parent, or whether it should simply declare the same methods independently. If the parent contract is likely to change, every child interface and implementing class will be affected. Prefer composition over inheritance when the relationship is not a strict "is-a" contract relationship. For example, a ReadableRepository might not need to extend Repository if the read operations are conceptually separate.

Performance and Runtime Behavior

Interface inheritance does not add a special application-level performance cost compared with normal interface dispatch. Calls through an interface reference use the invokeinterface bytecode instruction, but JIT compilers can inline and optimize such calls in practice. The real cost of deep interface hierarchies is maintenance and cognitive load, not execution speed.

One runtime consideration is that default methods are compiled as regular interface methods and can be overridden by classes. If a class overrides a default method, the JVM uses the class's implementation. This is standard polymorphism and does not introduce overhead. In practice, you should not avoid interface inheritance for performance reasons; instead, focus on keeping the contract hierarchy shallow and clear.

Java Interface Inheritance: Rules, Default Methods, and Design | RYUSLOG DEV