Abstract Classes vs Interfaces in Java
Executive Summary
Use an abstract class when related classes share state, constructors, or a fixed workflow with variable steps: the template method pattern. Use an interface when unrelated classes share a capability: implementing several is allowed, so interfaces compose where inheritance cannot. Abstract classes declare fields and concrete methods freely; interfaces allow only constants as fields and, since Java 8, default and static methods, which carry behavior but never state. The JDK’s own idiom is interface first, abstract skeleton second: List is an interface, AbstractList is the shared partial implementation, ArrayList extends the skeleton and implements the contract. Choose interface for what a thing can do, abstract class for what a family shares, and a record for what a thing carries. When neither fits, neither should be written.
Abstract Classes: Contract Plus State
An abstract class, declared with the abstract keyword, cannot be instantiated but can hold everything a normal class holds: fields, constructors, and concrete methods, plus abstract methods that subclasses must implement. It exists for one job: a family of closely related classes sharing structure, as the abstract lesson defines.
The classic use is the template method: a concrete method fixes the workflow, and abstract methods mark the steps each subclass varies. Here is the full example this article uses, a report exporter:
abstract class ReportExporter {
private final String name; // state: interfaces cannot hold this
protected ReportExporter(String name) { // constructor: for subclasses
this.name = name;
}
public final void export(String content) { // template: fixed workflow
validate(content);
byte[] bytes = render(content); // variable step
deliver(bytes); // variable step
System.out.println(name + ": exported " + bytes.length + " bytes");
}
private void validate(String content) {
if (content == null || content.isBlank()) {
throw new IllegalArgumentException("empty report");
}
}
protected abstract byte[] render(String content);
protected abstract void deliver(byte[] bytes);
}
class ConsoleExporter extends ReportExporter {
ConsoleExporter() {
super("console");
}
@Override
protected byte[] render(String content) {
return content.getBytes();
}
@Override
protected void deliver(byte[] bytes) {
System.out.println(new String(bytes));
}
}
public class ReportJob {
public static void main(String[] args) {
ReportExporter exporter = new ConsoleExporter();
exporter.export("quarterly summary"); // workflow fixed, steps plugged in
}
}
Notice what the abstract class is buying you. The export workflow lives once and cannot be changed per subclass (final). The name field and validation live with the family, so no subclass re-implements them. And the constructor ran through super exactly as the inheritance article taught. That combination, state plus skeleton, is the abstract class’s unique product.
Interfaces: Pure Capability
An interface, per the JLS interface chapter, is a contract: method names and signatures that adopters promise to implement. A class extends one parent but implements as many interfaces as it likes, which makes interfaces the language’s mechanism for multiple inheritance of type. Since Java 8, interfaces can carry default methods (behavior with a body) and static methods; what they still cannot carry is instance state.
The same exporter capability as an interface:
interface Exporter {
byte[] render(String content); // implementors supply the steps
void deliver(byte[] bytes);
default void export(String content) { // behavior composed from the steps
if (content == null || content.isBlank()) {
throw new IllegalArgumentException("empty report");
}
byte[] bytes = render(content);
deliver(bytes);
}
}
class HttpExporter implements Exporter {
@Override
public byte[] render(String content) { return content.getBytes(); }
@Override
public void deliver(byte[] bytes) { /* POST to a service */ }
}
The default method export gives every implementor the same workflow without inheritance. However, compare the two carefully: the interface version has no name field, no constructor, no per-family state. Any state a default method would want to use must live in the implementing class, untyped and unnamed by the contract. That gap, more than any keyword, is the design decision.
Abstract Classes vs Interfaces: One Comparison Table
| Feature | Abstract class | Interface |
|---|---|---|
| Instantiated directly | No | No |
| Instance fields (state) | Yes, any kind | No, only constants |
| Constructors | Yes, called via super | No |
| Concrete methods | Yes, any visibility | default and static (Java 8+), private (Java 9+) |
| Abstract methods | Yes | Yes |
| How many per class | One (extends) | Many (implements) |
| Access levels | All four | Public members; private helpers |
| Meaning | What a family is, with shared state | What any class can do |
| Typical pattern | Template method | Capability, API, strategy |
Default Methods: The Blurry Line and Its Limits
Default methods, added in Java 8, blur the line: interfaces can now ship behavior. The motivation was API evolution: Collection gained stream() and forEach() in Java 8 without breaking a single existing implementation, because the JDK shipped the behavior as a default. Without that mechanism, adding one method to a published interface would have broken every implementor on earth at once.
Three limits keep the line meaningful. Default methods cannot touch instance state, because interfaces have none. Diamond conflicts are real: if two interfaces both define the same default, the implementing class must override it or the code does not compile. And defaults are for evolution, not for smuggling a framework into an interface; behavior that needs state belongs in a class.
How Real Systems Do This
The JDK demonstrates the full idiom. List is an interface, so your class can be a List while extending something else. AbstractList is the abstract skeleton that ArrayList, LinkedList, and the immutable list implementations share, providing the iterator machinery from get(int) and size(). Comparable is an interface, and so is AutoCloseable, which try-with-resources in Part 3 uses. The pattern is consistent: contract as interface, shared skeleton as abstract class, concrete implementations at the leaves.
In my experience, teams get this wrong in one direction: they ship abstract classes where the ecosystem wanted interfaces. We published an abstract Report class in a shared library once, and within a month two teams needed to extend their own domain classes while also being reports, which the single-inheritance rule made impossible. We converted it to an interface plus an optional abstract helper, and both teams unblocked in a day. The lesson: publish capability as interfaces, because you cannot know your users’ inheritance budgets.
Modern Java adds two twists you will meet soon. Sealed interfaces restrict who can implement them, which lets the compiler verify exhaustive switches, as the sealed classes article shows. And interfaces with a single abstract method are functional interfaces, which lambdas implement as expressions: that is the foundation of the lambdas article in Part 3.
Decision Framework
Ask these in order, and the choice makes itself.
- Does the abstraction need instance state, a name, or per-family configuration? Abstract class.
- Is it a capability that unrelated classes can share? Interface.
- Is there a fixed workflow with variable steps to enforce across a family? Abstract class, with a final template method.
- Must implementors combine several contracts at once? Interface, because extends is a single ticket.
- Are you evolving an already-published API for new consumers? Interface plus a default method.
- Are you modeling a fixed set of variants, not classes at all? That is an enum, the next article.
- Are you modeling pure data? That is a record, and neither tool should be used.
When NOT to Use Each
- Do not use an abstract class for a stateless capability. It spends the subclass’s only inheritance ticket and offers nothing an interface gives; marker-style capabilities (Comparable, AutoCloseable) prove how little ceremony a contract needs.
- Do not use an interface to share code that requires state. The implementor ends up holding unnamed fields that the default methods cannot see or control, and the “shared logic” scatters across every implementation.
- Do not reach for either when a concrete class, a record, or an enum is honest enough. A two-implementation contract that nothing will ever substitute is speculative structure; the enums article and records cover the shapes that make abstractions unnecessary.
Common Mistakes
- Trying to instantiate an abstract type: new ReportExporter() does not compile, and neither does new Exporter(). Both are contracts, not products.
- An abstract class with only abstract methods: that is an interface wearing a costume, and it blocks multiple inheritance for every subclass.
- A default-method diamond left unresolved: implementing two interfaces with the same default must be overridden explicitly, or compilation fails.
- The constants interface: packaging constants in an interface for convenient inheritance pollutes every implementor’s namespace with fields that are not its business.
- God interfaces with fifteen methods: implementors stub their way through half of them. Split capabilities until each interface says one sentence.
- Choosing before understanding needs: the framework above answers itself only when you know where state, workflow, and combination live.
Key Takeaways
- Abstract classes carry state, constructors, and partial implementation down one inheritance line; interfaces carry pure capability in any number.
- Use the abstract class for families sharing a workflow: a final template method over abstract steps.
- Use the interface for capabilities that unrelated classes adopt and combine.
- Default methods let published APIs evolve without breaking implementors, but they carry behavior, never state.
- The JDK idiom is interface first, abstract skeleton second: List plus AbstractList, contract plus shared plumbing.
- Publish capability as interfaces, because you cannot know your consumers’ inheritance budgets.
- Fixed variant sets want enums, pure data wants records, and neither situation needs either tool.
FAQ
What is the difference between an abstract class and an interface in Java?
An abstract class can hold fields, constructors, and concrete methods, and a class can extend only one. An interface defines a contract with abstract, default, and static methods but no instance state, and a class can implement many. State and workflow point to the abstract class; capability and composition point to the interface.
Can an abstract class have constructors in Java?
Yes. The constructor runs during subclass creation through super(…), initializing the family’s shared state. You can never call it directly, because the abstract type itself cannot be instantiated.
Can a class implement multiple interfaces in Java?
Yes, any number, which is how Java provides multiple inheritance of type. If two interfaces define the same default method, the class must override it and may call both versions by name.
What are default methods in Java interfaces?
Interface methods with a body, introduced in Java 8 for API evolution. They give existing implementors new behavior for free, but they cannot access instance state, because interfaces have none.
When should I use an abstract class instead of an interface?
When closely related classes share instance state, a constructor, or a fixed workflow with variable steps. If the abstraction is a capability without state, use an interface so implementors keep their inheritance slot.
Conclusion
Abstract classes and interfaces are both contracts, but they sell different things: the abstract class sells structure to a family, and the interface sells capability to anyone. Choose by asking where state lives, whether the workflow is fixed, and whether implementors will ever need to combine this contract with others.
The next article covers the tool that beats both for a specific case: when a type has a fixed set of variants, enums model it with less code and more guarantees than any class hierarchy.
Interface for what it can do, abstract class for what it shares, record for what it carries. Nine out of ten design reviews end at that sentence.
Last updated on 27 September 2026.
