Java Interface Inheritance: Extending Contracts
java interface inheritance: Understand how Java interface inheritance works: extending interfaces, multiple inheritance, default method resolution, and practical desig...
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. This is java interface inheritance, and it is a core mechanism for building layered APIs in Java. 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, but static methods are not inherited in the sense of overriding; they are hidden and must be accessed through the declaring interface.
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 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 declare methods with the same signature.
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 only occurs when both provide a default implementation. In that case, the child must override the method to disambiguate, or it will fail to 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 the class implements multiple interfaces that provide conflicting default methods, the class must override the method explicitly, even if the interfaces are related through inheritance.
How Class Implementation Interacts with Interface Inheritance
When a 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 the class implements multiple interfaces that share a method signature, the class 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 { // Cannot override static print() 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 has no direct performance cost. The JVM resolves method calls through the invokeinterface bytecode instruction, which is slightly different from invokevirtual for class methods, but modern JIT compilers optimize both to similar native code. The real cost of deep interface hierarchies is maintenance and cognitive load, not execution speed. When a method is called through an interface reference, the JVM uses a method table that includes inherited methods, so lookup time does not grow with the number of interfaces.
One runtime consideration is that default methods are compiled as regular instance 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 your contract hierarchy shallow and clear.