Interface vs abstract class: when should I use which?
Since Java 8, interfaces can have default and static methods, so the two look alike. Decide by what you need.
Choose an interface when
- You're defining a capability or contract:
Comparable,Runnable,PaymentGateway,NotificationSender. - Unrelated classes should share it (a Course and a User can both be Exportable).
- You want to stay flexible: a class can implement many interfaces but extend only one class.
- You want easy swapping and mocking (Spring injects interfaces; tests pass fakes).
- You want lambdas: only interfaces can be functional.
Choose an abstract class when
- Related classes share state (fields) and real code.
- You need constructors, protected members or controlled initialisation.
- You want a fixed algorithm with steps that subclasses fill in (the Template Method pattern).
Often both: an interface for the contract plus an abstract base class that implements the boring parts. The JDK does this with List and AbstractList.
A quick test: if you'd describe it with "can …" or "is able to …", make an interface. If you'd say "is a kind of … and shares …", consider an abstract class.
Side by side
| Interface | Abstract class | |
|---|---|---|
| Purpose | A contract: what a type can do | A partial implementation: shared state and code |
| Instance fields | No (only constants) | Yes |
| Constructors | No | Yes (called via super(...)) |
| Methods | abstract, default, static, private (Java 9) | Any: abstract and concrete |
| Access modifiers | Methods are public (or private helpers) | Any: public, protected, package-private, private |
| Inheritance | A class can implement many | A class can extend only one |
| Lambdas | Yes, if it has one abstract method | No |
| Adding a method later | Add a default method: nothing breaks | Add a concrete method: nothing breaks |
| Typical examples | Comparable, List, Runnable | AbstractList, HttpServlet, InputStream |
Example
// Interface: a capability; any class can have it
public interface PaymentGateway {
PaymentResult charge(String orderId, long amountPaise);
default boolean supportsUpi() { return false; } // Java 8: evolve without breaking implementations
}
public class RazorpayGateway implements PaymentGateway {
public PaymentResult charge(String orderId, long amountPaise) { /* call Razorpay */ return PaymentResult.ok(); }
@Override public boolean supportsUpi() { return true; }
}
// Abstract class: shared state + a fixed algorithm (Template Method)
public abstract class ReportGenerator {
private final Clock clock; // shared state
protected ReportGenerator(Clock clock) { this.clock = clock; }
public final String generate() { // the steps can't be reordered
return header() + "\n" + body() + "\nGenerated " + LocalDate.now(clock);
}
protected String header() { return "JavaAtlas report"; } // a default step
protected abstract String body(); // subclasses must fill this in
}
public class SalesReport extends ReportGenerator {
public SalesReport(Clock clock) { super(clock); }
@Override protected String body() { return "Revenue: ₹29,980"; }
}Common mistake
Creating an abstract "BaseService" just to share a few helper methods. Every service is then stuck with one parent forever; use composition or a small helper class instead.
Under the hood
Default methods exist so libraries can evolve: Java 8 added stream() to Collection and sort() to List without breaking every implementation ever written. They can't hold state, though, so if you catch yourself wishing an interface had a field, you want an abstract class (or composition). Since Java 17, sealed interfaces and classes can also restrict who implements them, which is great for fixed sets of types such as payment results.
Check yourself
Which one can declare instance fields?
How this connects
Know these first
Where this leads
You've reached the end of this thread. Try a learning path for what's next.
Part of OOP in practice.
Was this lesson helpful?
Finished reading? Mark it complete to track your progress.