Choosing Between Interface and Abstract Class in Java
Fast recall
Quick revision
Core answer
Whether you can pick the right tool based on shared state vs capability and multiple-type needs.
Always using abstract classes for code sharing, or creating an interface where no variation/abstraction is needed.
Mention you can combine both — an interface for the contract plus an abstract base providing partial implementation (e.g. AbstractList implements List).
Interview-ready guide
Interview answer
I treat this as a design decision rather than a syntax one, and I start by asking what I'm modeling. If it's a capability — something a type can do, like Runnable or Comparable — I use an interface, because even unrelated types can implement it and a class can implement as many as it needs. Interfaces are also right when callers should depend on a contract and swap implementations, so a List reference works whether it's backed by an ArrayList or a LinkedList.
I reach for an abstract class only when a group of subclasses are clearly the same kind of thing and genuinely share state and code — a common color or id field, a shared helper, or a template method where the base fixes the algorithm and leaves a couple of holes for each subclass. HttpServlet is the classic example: the lifecycle lives in the base and you just override doGet or doPost.
What I'd avoid is grabbing an abstract class just to reuse code, since that spends your one inheritance slot — default methods often share code without that cost. So my default is interface-first, and the thing I'd add is that these aren't either-or: you can define the contract as an interface and provide a skeletal abstract base alongside it, which is exactly what AbstractList does for List in the JDK.
Choosing Between Interface and Abstract Class in Java
Direct Answer
Use an interface to define a capability that many (even unrelated) types can have, when you need multiple inheritance of type, or to decouple callers from implementations. Use an abstract class when related subclasses share state and concrete code and you want a common base with a template. Default to interfaces; reach for an abstract class when shared implementation/state is real.
Code Example
// ── INTERFACE: capability, unrelated types can implement ─────────────────
interface Notifier {
void send(String message); // contract
default void sendWithTimestamp(String msg) { // Java 8 default — shared helper
send("[" + java.time.LocalTime.now() + "] " + msg);
}
}
class EmailNotifier implements Notifier {
@Override public void send(String msg) { System.out.println("EMAIL: " + msg); }
}
class SmsNotifier implements Notifier {
@Override public void send(String msg) { System.out.println("SMS: " + msg); }
}
// Caller depends on the interface — swap implementations freely:
Notifier n = new EmailNotifier();
n.sendWithTimestamp("Order confirmed"); // [10:05:01] Order confirmed
n = new SmsNotifier();
n.sendWithTimestamp("Order shipped"); // [10:05:02] Order shipped
// ── ABSTRACT CLASS: shared state + template method ────────────────────────
abstract class Game {
private String name; // shared instance field
private int score; // shared instance field
Game(String name) { this.name = name; } // constructor
// Template method: defines the algorithm, delegates the details
public final void play() {
start();
score = computeScore();
end();
}
abstract void start(); // each game decides how it starts
abstract int computeScore(); // each game has its own scoring
void end() { // shared concrete: no need to repeat
System.out.println(name + " finished. Score: " + score);
}
}
class Chess extends Game {
Chess() { super("Chess"); }
@Override void start() { System.out.println("Chess: setting up board"); }
@Override int computeScore() { return 42; }
}
// ── COMBINE: interface contract + abstract partial implementation ──────────
interface List<E> { // contract (simplified)
E get(int index);
int size();
default boolean isEmpty() { return size() == 0; } // default built on the contract
}
abstract class AbstractList<E> implements List<E> { // skeletal implementation
// get() and size() still abstract — subclasses must provide these
// other List methods can be built on top of get() and size() here
}
class SimpleList<E> extends AbstractList<E> { // only fill the two gaps
private Object[] data;
private int count;
SimpleList() { data = new Object[10]; }
@Override public E get(int i) { return (E) data[i]; }
@Override public int size() { return count; }
}
public class Demo {
public static void main(String[] args) {
Game chess = new Chess();
chess.play();
// Chess: setting up board
// Chess finished. Score: 42
}
}
Common Mistakes & Pitfalls
- Reaching for an abstract class just to share code — it spends the single inheritance slot; an interface with
defaultmethods often reuses the same code without locking subclasses into a hierarchy. - Creating an interface where there's no variation — adding a contract with exactly one implementation and no second type in sight is needless indirection, not flexibility.
- Not knowing you can combine both — the
AbstractList implements Listpattern (contract plus skeletal base) is what interviewers look for; missing it makes you sound like you only know one tool. - Choosing by keyword instead of by design — the decision is about shared state versus capability, not
implementsversusextends; picking by habit leads to rigid hierarchies you can't extend later.
Check your understanding
Answer these without looking back at the article.
- Can you give a real-world example from the Java standard library that uses abstract classes?
- What is the Template Method pattern and how does it use abstract classes?
- What is the 'favor interfaces over abstract classes' principle?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.