Home
Java from First Principles / Chapter 7 — Abstract Classes vs Interfaces

Abstract Classes vs Interfaces

The most-asked Java interview question. The real differences, when to pick each, and how default methods in Java 8+ blurred the lines.


The interview question that won't die

Open any list of Java interview questions and "what's the difference between an abstract class and an interface?" is in the top five. Some hiring managers ask it on autopilot. Some candidates rattle off a memorised answer.

The reason it keeps coming up is that the answer used to be simple — until Java 8 added default methods to interfaces in 2014, blurring the lines. Now the right answer depends on which Java version you're working with and what you're trying to do.

This chapter walks through both, what's actually different, and gives a practical decision tree for when to use which. By the end you'll have an answer worth more than a memorised list.

Abstract class versus interface: what each can and can't do Abstract class vs Interface Abstract class Can do? Interface Instance fields Constants (public static final) Constructors Abstract methods Concrete methods default single only Multiple inheritance multiple Holds state "default" = since Java 8, interfaces can have default method bodies — but still no state
An abstract class is "an unfinished class" — it has state, can have constructors, can do most things a concrete class does. An interface is a pure contract — it describes what something can do, not what data it carries. Modern Java blurs the line slightly with default methods, but the state distinction remains.

Abstract classes

An abstract class is a class that's marked with the abstract keyword and cannot itself be instantiated. It serves as a partial implementation — a base for subclasses to build on.

Java
public abstract class Shape {
    private String color;

    public Shape(String color) {
        this.color = color;
    }

    public String getColor() {
        return color;
    }

    public abstract double area();      // no body — subclasses must implement
    public abstract double perimeter();
}

new Shape("red");    // compile error — cannot instantiate abstract class

public class Circle extends Shape {
    private double radius;

    public Circle(double radius, String color) {
        super(color);
        this.radius = radius;
    }

    @Override
    public double area() {
        return Math.PI * radius * radius;
    }

    @Override
    public double perimeter() {
        return 2 * Math.PI * radius;
    }
}

Circle c = new Circle(5.0, "red");   // fine — Circle is concrete

The abstract class can have:
- **State** (instance fields) — yes
- **Constructors** — yes (called by subclasses via super(...))
- **Concrete methods** with full implementations — yes
- **Abstract methods** (declared, not defined) — yes; subclasses MUST implement

Use an abstract class when you have:
1. A common base with shared *state* (fields) that all subclasses need.
2. Some methods are fully implemented and shouldn't be overridden.
3. Other methods are intentionally left for subclasses.
4. A clear "is-a" relationship — Circle IS a Shape.


Interfaces

An interface defines a contract — a set of methods a class promises to implement. It's the purest form of abstraction.

Java
public interface Drawable {
    void draw();
    void erase();
}

public class Circle implements Drawable {
    @Override
    public void draw() { ... }

    @Override
    public void erase() { ... }
}

A class can implement many interfaces:

Java
public class Circle implements Drawable, Serializable, Comparable<Circle> {
    // must implement all methods from all three interfaces
}

Compare this to extending classes: a class can only extend ONE other class. Interfaces give you multi-inheritance, sort of — you can pick up contracts from many sources.

**Before Java 8, interfaces could only contain:**
- Abstract method signatures
- Public static final constants

**Since Java 8, interfaces can also have:**
- default methods (concrete implementations that classes inherit but can override)
- static methods (utility helpers on the interface itself)

**Since Java 9, interfaces can also have:**
- private methods (helpers shared between default methods, not exposed)

Java
public interface Vehicle {
    void start();       // abstract — implementers must provide

    default void honk() {
        System.out.println("Beep!");
    }

    static Vehicle createDefault() {
        return new BasicCar();
    }

    private String logPrefix() {
        return "[Vehicle] ";
    }
}

The key thing interfaces still **cannot** have: instance state. No mutable fields. They're a contract, not a class. They describe what something can do, not what data it carries.


The actual differences

The table you came for. Read every row carefully — some are obvious, some are not.

| Capability | Abstract class | Interface |
|---|---|---|
| Instance fields (mutable state) | Yes | No — only public static final constants |
| Constructors | Yes (subclasses call via super) | No |
| Method bodies (full implementations) | Yes | Yes, via default (since Java 8) |
| Abstract method declarations | Yes | Yes |
| Inheritance — how many can a class get? | One only (single inheritance) | Many (multiple) |
| Access modifiers on methods | All four (public/protected/package/private) | Mostly public; private since Java 9 |
| Can be instantiated | No | No |
| Designed for | "is-a" hierarchies sharing state and behaviour | Capability contracts independent of class hierarchy |

The two facts that matter most in practice:

1. **An abstract class has state. An interface does not.** This is the single most important distinction. If your contract needs to hold any data per instance, you need an abstract class (or a concrete class).

2. **A class can implement many interfaces but extend only one class.** Interfaces are how Java handles "this thing has multiple capabilities." A Car might be Drivable, Serializable, Comparable, all at once.

The other differences (constructors, access modifiers) follow from these two.


When to pick abstract class

Use an abstract class when ALL of the following are true:

**1. The relationship is genuinely hierarchical ("is-a").** A Circle IS a Shape. A Dog IS an Animal. The taxonomy is part of the design, not just a code-reuse trick.

**2. The base class has shared state.** All subclasses naturally have certain fields. If a Shape always has a colour and a position, that lives in the abstract class.

**3. You want partial implementations.** Some methods have a sensible default; subclasses just need to fill in the specific bits.

**4. The hierarchy is stable.** You're confident new subclasses will fit this shape. (Inheritance is much harder to refactor than composition once it spreads.)

**Real-world example.** The Java standard library has AbstractList<E>. It implements most of the List<E> interface methods (iteration, equals, hashCode, toString, indexOf, etc.) in terms of two abstract primitives: get(int) and size(). If you want to write a custom List, you extend AbstractList, implement those two methods, and you get the rest for free. ArrayList extends AbstractList. So does Vector. So does Stack.

Java
public abstract class AbstractList<E> implements List<E> {
    // Concrete methods that work for any List with these two primitives:
    @Override
    public boolean contains(Object o) {
        Iterator<E> it = iterator();
        while (it.hasNext()) {
            if (Objects.equals(o, it.next())) return true;
        }
        return false;
    }
    
    // ... many more concrete methods
    
    // Abstract methods that subclasses must define:
    @Override
    public abstract E get(int index);
    
    @Override
    public abstract int size();
}

That's an abstract class doing real work. It captures the algorithmic reality of "what every List does" while leaving the storage strategy to subclasses.


When to pick interface

Use an interface when ANY of the following are true:

**1. You're defining a capability that cuts across class hierarchies.** Comparable, Serializable, Cloneable, Iterable, AutoCloseable — any class can implement these regardless of what they extend.

**2. The contract has no inherent state.** A Drawable thing has a draw() method. Nothing about being drawable requires holding particular data.

**3. You want loose coupling.** Code written against an interface doesn't depend on any specific implementation. This is the foundation of dependency injection, plugin architectures, and testability.

**4. You need multiple inheritance of behaviour.** A class implements many interfaces; an abstract class only inherits from one abstract class.

**5. You're designing the public face of an API.** Interfaces are the long-term contract. Implementations can change. The Java collections framework is built on interfaces (List, Set, Map, Queue) precisely so the implementations can evolve.

**Real-world example.** Almost every Java framework or library defines its public API as interfaces:

Each of these is an interface so multiple implementations can coexist and be swapped. JDBC has dozens of Connection implementations (one per database driver). Spring's BeanFactory has several. Logging has Log4j, Logback, JUL — each with their own classes implementing the same Logger interface.

You can't do this with abstract classes (single inheritance), and you'd have to forcibly fix state if you tried.


When to use both together

The most professional Java code often uses **interface + abstract class** as a pair. Like this:

Java
public interface Cache<K, V> {
    V get(K key);
    void put(K key, V value);
    void evict(K key);
}

public abstract class AbstractCache<K, V> implements Cache<K, V> {
    // Concrete bookkeeping shared by all caches
    private long hits = 0;
    private long misses = 0;

    @Override
    public V get(K key) {
        V value = doGet(key);
        if (value != null) hits++; else misses++;
        return value;
    }

    public double hitRate() {
        long total = hits + misses;
        return total == 0 ? 0.0 : (double) hits / total;
    }

    // Subclass implements just the storage
    protected abstract V doGet(K key);

    @Override
    public abstract void put(K key, V value);

    @Override
    public abstract void evict(K key);
}

public class LRUCache<K, V> extends AbstractCache<K, V> { ... }
public class LFUCache<K, V> extends AbstractCache<K, V> { ... }
public class TTLCache<K, V> extends AbstractCache<K, V> { ... }

Now:
- Code written against Cache<K, V> works with all implementations.
- Implementations get hit/miss tracking and hitRate() for free via the abstract class.
- Tests can use mock Cache implementations directly without needing to subclass anything.

This pattern shows up everywhere in well-designed Java codebases. Interface defines what; abstract class provides a partial how; concrete classes finish the job.

The Java standard library's collection classes follow this pattern almost universally: Collection interface → AbstractCollection partial impl → ArrayList, LinkedList, etc. concrete classes.


Default methods — the Java 8 game-changer

Java 8 (2014) added default methods to interfaces. They were added specifically to solve a problem: the standard library wanted to add new methods like stream() and forEach() to Collection and List, but adding methods to an interface would break every existing class that implemented those interfaces in older code.

The fix: let interfaces provide default implementations. Existing classes inherit the default automatically; they can override it if they want.

Java
public interface Greeter {
    String name();

    default void greet() {
        System.out.println("Hello, " + name() + "!");
    }
}

public class Friend implements Greeter {
    @Override
    public String name() { return "Alice"; }
    // greet() inherited from the interface
}

new Friend().greet();    // "Hello, Alice!"

This made interfaces more powerful and blurred the line with abstract classes. People started asking: now that interfaces can have method bodies, why ever use abstract classes?

**The answer: state.** Interfaces still cannot hold instance fields. Default methods can call other methods on this, but cannot maintain mutable state across calls. If your "implementation" needs to remember anything between calls, you need a class — abstract or concrete.

Default methods are perfect for:
- **Convenience methods built on top of primitives.** List.sort() is a default method that delegates to a Comparator. Collection.removeIf() is a default method that uses iterator() and remove().
- **Backward-compatible additions** to old interfaces.
- **Mixin-style capabilities** that compose from interface methods.

They're not good for:
- Anything stateful.
- Heavy implementation logic — that's a smell that the behaviour should be in a class.
- Complex hierarchies where the diamond problem might apply (Java has rules to handle this, but it gets confusing fast).


Practical decision tree

Here's the question to ask, in order:

**1. Does the contract need instance state?**
- Yes → abstract class (or concrete class with composition).
- No → keep going.

**2. Will implementing classes have other base classes they need to extend?**
- Yes (or you're not sure) → interface.
- No → keep going.

**3. Is the relationship a genuine "is-a" taxonomy?**
- Yes, and there's significant shared implementation → consider both: interface for the contract, abstract class as a base for the common impl.
- Yes, but no shared implementation needed → interface.
- No, it's a capability or mixin → interface.

**4. Are you designing a public API others will implement?**
- Yes → interface. Always interface. Even if you also offer an abstract class to make it easier.
- No, it's internal-only → abstract class might be more direct.

**Modern Java default.** Default to interface unless you specifically need state. The combination "interface for the public contract + optional abstract class implementation" is the most flexible and matches how the standard library is designed.


Common interview answers and what's actually right

A few interview answers you'll hear repeated, and a more accurate framing.

**"Use an interface when you want multiple inheritance."**

Technically true. But that's not really the question — the question is whether you NEED capability composition. Most classes don't need 5 interfaces; most need 1 or 2. The deeper truth is that interfaces enable loose coupling, not just multiple inheritance.

**"Use an abstract class when you have common code to share."**

Partially true. You should also consider: could that common code be a default method on an interface? Or a helper static method? Or a separate utility class? Sharing code via inheritance is one tool, not the only tool.

**"Interfaces define what; abstract classes define how."**

Was true before Java 8. Now interfaces can also define some "how" via default methods. The cleaner distinction: interfaces define contracts; abstract classes provide partial implementations with state.

**"Use whichever you feel like — they're basically the same."**

Wrong. The state-or-no-state question is real. The single-vs-multiple inheritance question is real. They're not interchangeable.

**"Java has both because of historical reasons."**

Also wrong, or at least misleading. Both exist because they solve different problems. Default methods narrowed the gap but didn't close it. State and constructor support are fundamentally a class thing.

If an interviewer asks the basic question, give the table answer: interface = contract + multiple inheritance + no state; abstract class = partial impl + single inheritance + state allowed. Then add: "In practice, I default to interface unless I need state, and I often combine them — interface for the public contract, abstract class as an optional helper." That's the senior answer.


⁂ Back to all modules