Skip to main content

Interfaces vs. Abstract Classes: Modern Java Differences

Medium

Interview Question: "Since Java 8 let interfaces have real implementations, the line between interfaces and abstract classes got blurry. What's actually still different between them?"

The short version​

Interfaces can now write real code too - default, static, and private methods - so "interfaces are just empty contracts" isn't quite true anymore. But one thing never changed: state.

An abstract class can hold mutable instance fields and set them up in a constructor, which is exactly why a class can only extend one - you can't have two sets of conflicting state. An interface is still stateless: no instance fields, no constructor, and a class can implement as many as it wants.


What Java 8 and 9 actually added to interfaces​

Before Java 8, an interface could only declare abstract methods - no bodies at all. That changed:

  1. default methods (Java 8): added mainly so library maintainers could add new methods to existing interfaces - like Collection.stream() - without breaking every class that already implements them.
  2. static methods (Java 8): utility methods tied to the interface itself, like Comparator.comparing(). Called directly as InterfaceName.methodName(), and implementing classes can't override them.
  3. private methods (Java 9): internal helpers that let two default methods share logic, without exposing that helper to anyone implementing the interface.

What's still fundamentally different​

FeatureAbstract ClassModern Interface (Java 8 / 9+)
Instance state✅ Can hold mutable instance variables❌ Stateless - only constants (public static final)
Constructors✅ Has one, to set up state via super()❌ None. Can't be instantiated
How many you can combineOne (extends a single class)Many (implements as many as you want)

When default methods collide​

Since a class can implement several interfaces, two of them might hand it the same default method. Java resolves this with three fixed rules.

Rule 1: a real class always wins​

If a class inherits a concrete method from its superclass, and also gets a default method with the same signature from an interface, the superclass version wins outright - the interface's default is ignored completely.

This is also why an interface can never override Object methods like toString(), equals(), or hashCode() with a default implementation - the class hierarchy always wins over an interface default.

Rule 2: the more specific interface wins​

If InterfaceB extends InterfaceA and both define the same default method, InterfaceB's version wins - the more specific interface beats the general one.

Rule 3: two unrelated interfaces with the same default = compiler error​

If a class implements two independent interfaces that both define the same default method, Java won't guess which one you meant. It refuses to compile: class inherits unrelated defaults for method() from types InterfaceA and InterfaceB

CloudServiceINTERFACEdefault connect() { ... }OnPremServiceINTERFACEdefault connect() { ... }COMPILE ERROR: AMBIGUOUS DEFAULT METHODHybridClientEXPLICIT OVERRIDECloudService.super.connect();Disambiguated explicitly by calling specific interface
Two unrelated interfaces provide HybridClient with conflicting default methods. Java refuses to compile until you explicitly choose.

The fix: override the method yourself in the class. Inside, you can write new logic or explicitly delegate to one of the interfaces:

InterfaceA.super.methodName();

Clean Code Example​

Here is how multiple inheritance method collisions are resolved across different languages:

interface CloudService {
default void connect() {
logAttempt();
System.out.println("Connecting to Cloud Service...");
}

// Java 9: Private helper method shared within the interface
private void logAttempt() {
System.out.println("Initiating network handshake...");
}
}

interface OnPremService {
default void connect() {
System.out.println("Connecting to On-Premises Server...");
}
}

// Conflict Resolution: HybridClient implements two interfaces with identical connect() methods
class HybridClient implements CloudService, OnPremService {

// MUST explicitly override to resolve ambiguity
@Override
public void connect() {
// Explicitly choose which default implementation to execute via InterfaceName.super
CloudService.super.connect();
System.out.println("Hybrid fallback established.");
}
}