Back to Blog
Java

Java Interface vs Abstract Class: How to Choose

java interface vs abstract class: Understand the practical differences between Java interfaces and abstract classes, including state, multiple inheritance, and API evo...

JavaInterfacesAbstract ClassesOOP DesignDefault MethodsInheritance
Diagram comparing a Java interface and an abstract class showing state inheritance and multiple implementation contracts

Every Java developer eventually faces the java interface vs abstract class decision when designing a class hierarchy. The choice shapes how the rest of the codebase can evolve—whether subclasses can inherit state, whether a class can participate in multiple contracts, and how future API changes affect existing implementations.

What an Abstract Class Provides That an Interface Does Not

An abstract class can declare instance fields, define constructors, and provide concrete method implementations that subclasses inherit. This matters when several related classes share both behavior and internal state.

public abstract class BaseRepository { protected final DataSource dataSource; protected BaseRepository(DataSource dataSource) { this.dataSource = dataSource; } protected Connection openConnection() { return dataSource.getConnection(); } public abstract void save(Entity entity); }

The constructor runs when a subclass is instantiated, so the dataSource field is initialized before any subclass code executes. An interface cannot do this—it has no instance fields other than static final constants and no constructors.

Interfaces Define Contracts Without Implementation Details

An interface specifies what a class can do, not how it does it. Before Java 8, every method in an interface was implicitly public abstract. With default methods, an interface can provide a fallback implementation, but it still cannot hold instance state.

public interface Sortable { void sort(); default void sortAndPrint() { sort(); System.out.println("Sorted"); } }

The default method sortAndPrint is inherited by any implementing class unless the class overrides it. This is useful for adding behavior to an interface without forcing every implementation to change.

Multiple Inheritance: The Decisive Constraint

A Java class can implement any number of interfaces but can extend only one class. This single constraint often decides the choice.

public interface Readable { void read(); } public interface Writable { void write(); } public class FileHandler implements Readable, Writable { @Override public void read() { // implementation } @Override public void write() { // implementation } }

If FileHandler also needed to extend BaseRepository, that is still possible—extending one class and implementing multiple interfaces is the standard Java pattern. The restriction is that you cannot inherit implementation from two classes.

Default Methods and the Diamond Problem

Java 8 introduced default methods, which allow an interface to provide a method body. This creates a potential conflict when a class implements two interfaces that both declare the same default method.

public interface A { default void log() { System.out.println("Log from A"); } } public interface B { default void log() { System.out.println("Log from B"); } } public class C implements A, B { @Override public void log() { A.super.log(); } }

The class must override the conflicting method and explicitly select which interface's default to call. This is the diamond problem, and it is why abstract classes—which allow only single inheritance—never face this ambiguity.

Runtime Behavior and Method Dispatch

Both abstract class methods and interface methods are dispatched virtually at runtime, so there is no meaningful performance difference in ordinary application code. The JVM handles both through the same virtual dispatch mechanism.

The practical difference is in how the JVM treats the types. An abstract class establishes an is-a relationship with its subclass. An interface establishes a capability contract. Code that accepts an interface parameter is more decoupled:

public void process(Sortable item) { item.sort(); }

This method accepts any class that implements Sortable, regardless of where that class sits in its own inheritance hierarchy. If you used an abstract class instead, the method would accept only subclasses of that abstract class.

When to Choose Each

Choose an interface when you need to define a contract that multiple unrelated classes can implement, when you need multiple inheritance of type, or when you want to keep implementation details entirely in the implementing class. Interfaces are also the right choice for APIs that third parties will implement.

Choose an abstract class when several related classes share internal state that must be initialized through a constructor, when you need to provide partial implementation with protected helper methods, or when the classes form a clear hierarchy with shared behavior.

A Practical Pattern: Combining Both

A common production pattern is to define an interface as the public contract and an abstract class as a base implementation. This gives callers a stable API while providing a convenient starting point for implementations.

public interface PaymentProcessor { void process(Payment payment); void refund(Payment payment); } public abstract class AbstractPaymentProcessor implements PaymentProcessor { protected final PaymentGateway gateway; protected AbstractPaymentProcessor(PaymentGateway gateway) { this.gateway = gateway; } @Override public void refund(Payment payment) { gateway.refund(payment.getTransactionId()); } }

Callers depend on PaymentProcessor, not on the abstract class. Implementations can extend AbstractPaymentProcessor to inherit the refund logic, or implement the interface directly if they need to extend another class.

API Evolution: Default Methods vs Abstract Class Changes

Adding a method to an interface breaks every implementing class unless the method has a default implementation. Adding a concrete method to an abstract class does not break subclasses—they simply inherit it. This is a critical consideration for library maintainers.

If you control all implementations, adding an abstract method is fine. If external teams implement your interface, a new abstract method forces them to update their code. A default method avoids that breakage, but it also means the default behavior may not fit every implementation.

This is why modern Java libraries often ship interfaces with default methods for optional behavior, while keeping required operations abstract.

java interface vs abstract class: Practical Usage and Code E | RYUSLOG DEV