Interfaces vs abstract classes in Java
"Interface or abstract class?" is one of the most common Java design questions — in interviews and in real code. Both let you program to an abstraction instead of a concrete type, but they answer different needs. An abstract class is a partially built base for a family of related classes; an interface is a contract any class can sign, even unrelated ones. Since Java 8 the line has blurred (interfaces now carry behavior), so the real skill is knowing what each one can't do and choosing on that basis. This guide walks through both, the rules that govern conflicts between them, and the judgment that makes the choice obvious.
Abstraction: why both exist
Abstraction means exposing a simplified contract while hiding the implementation behind it. Callers depend on the abstract type, not the concrete class — so you can swap implementations freely. This is the heart of "program to an interface, not an implementation."
interface PaymentGateway { void charge(int cents); }
class StripeGateway implements PaymentGateway {
public void charge(int cents) { /* HTTP calls, retries, auth all hidden here */ }
}
PaymentGateway gw = new StripeGateway(); // calling code knows only the contract
gw.charge(1999); // swap in PayPalGateway later, no caller changes
Both interfaces and abstract classes give you this decoupling. The difference is how much they let you share alongside the contract — and that is what drives the choice.
What an abstract class can do
An abstract class is declared abstract and cannot be instantiated with new — it
exists to be extended. Its superpower is that it can hold real state (instance fields),
define a constructor, and mix abstract methods (no body, subclasses must implement)
with concrete methods (shared implementation). A class extends exactly one.
abstract class Shape {
private final String name; // instance STATE — interfaces can't do this
Shape(String name) { this.name = name; } // constructor runs during subclass construction
abstract double area(); // subclasses MUST implement
String describe() { // shared concrete behavior, reuses area()
return name + " has area " + area();
}
}
class Circle extends Shape {
private final double r;
Circle(double r) { super("circle"); this.r = r; } // must chain to super(...)
double area() { return Math.PI * r * r; }
}
// Shape s = new Shape("x"); // ERROR — abstract, cannot instantiate
Note that a class with any abstract method must itself be abstract — but an abstract
class is allowed to have zero abstract methods (sometimes useful purely to forbid
direct instantiation). Think of an abstract class as a half-finished base you're not meant
to use directly.
Constructors and shared state
A frequent interview gotcha: can an abstract class have a constructor? Yes. You can't
new it directly, but its constructor runs when a subclass is instantiated, via the
implicit or explicit super(...) call. That's exactly how the abstract class initializes
its slice of the object's state.
abstract class Account {
protected final String owner;
private double balance;
Account(String owner) { this.owner = owner; } // initializes shared state
void deposit(double amt) { balance += amt; } // shared, operates on shared state
abstract double interestRate(); // each subtype defines its own
}
class Savings extends Account {
Savings(String owner) { super(owner); } // constructor chaining is mandatory
double interestRate() { return 0.04; }
}
Interfaces, by contrast, cannot have constructors — they have no instance state to initialize. The moment your abstraction needs to set up shared mutable fields at construction time, you need an abstract class, not an interface.
What an interface can do
An interface is a contract. Historically it held only abstract methods, but modern Java
gives it default, static, and private methods too. The hard limits: no instance
state (only public static final constants), no constructors, and a class can implement
many interfaces. Every member is implicitly public.
interface Validator {
boolean valid(String s); // abstract — the contract
static Validator notEmpty() { // STATIC — factory tied to the type
return s -> s != null && !s.isEmpty();
}
default boolean validAll(List<String> xs) { // DEFAULT — optional shared behavior
return xs.stream().allMatch(this::isOk); // delegates to a private helper
}
private boolean isOk(String s) { return valid(s); } // PRIVATE — DRY for default methods
}
The three method flavors each earn their keep: default lets interfaces evolve
without breaking implementers (this is how forEach was added to Iterable in Java 8);
static holds factories and utilities tied to the type (like Comparator.comparing);
private lets default/static methods share code without exposing it. Interfaces are now
mini-toolkits, but still pure of mutable state.
Constants, not fields
Interfaces can hold data — but only constants. Every field is implicitly
public static final, so it must be initialized at declaration and can never change. There
is no per-instance storage.
interface Physics {
double SPEED_OF_LIGHT = 299_792_458; // implicitly public static final
}
double c = Physics.SPEED_OF_LIGHT; // access through the interface name
Beware the old "constant interface" anti-pattern — implementing an interface just to
inherit its constants pollutes your type's public API with an implementation detail. Prefer
an enum or a final class with a private constructor for grouped constants. An interface
holds constants and behavior; never mutable state.
Multiple inheritance of type
The single biggest practical reason to reach for an interface: a class can implementsmany interfaces, but extends only one class. Interfaces give Java multiple
inheritance of type (and, since Java 8, of behavior via defaults) without multiple
inheritance of state — which is what causes the classic diamond problem.
interface Swimmer { default void move() { System.out.println("swim"); } }
interface Flyer { default void glide() { System.out.println("glide"); } }
class Duck implements Swimmer, Flyer { } // inherits BOTH capabilities, no conflict
new Duck().move();
new Duck().glide();
Because interfaces hold no instance fields, there is no ambiguous state to merge — that's precisely why Java permits multiple interfaces but only single class inheritance. Interfaces are safe multiple inheritance of capability; classes are single inheritance of implementation.
Resolving default-method conflicts
Multiple inheritance of behavior does introduce one ambiguity: what if two interfaces supply
conflicting default methods? The compiler refuses to guess — the class must
override to break the tie, and can delegate to a specific parent with the special
Interface.super.method() syntax.
interface A { default String hi() { return "A"; } }
interface B { default String hi() { return "B"; } }
class C implements A, B {
@Override public String hi() {
return A.super.hi(); // explicitly pick A's default (or write your own)
}
}
There's also a precedence rule when a class and an interface collide: class always beats
interface. A concrete method inherited from a superclass wins over an interface default,
even if the interface seems "more specific." Among interfaces alone, a more-specific
subinterface's default beats a less-specific one; genuine ties you resolve by hand. The
ordering to memorize: superclass method > interface default > you must override.
Functional interfaces
A functional interface has exactly one abstract method (SAM — Single Abstract
Method), which makes it the target type of a lambda or method reference. default
and static methods don't count against the limit. The @FunctionalInterface annotation
asks the compiler to enforce the one-abstract-method rule, so an accidental second abstract
method becomes a compile error.
@FunctionalInterface
interface Transformer {
String apply(String s); // the single abstract method (SAM)
default Transformer andTrim() { // defaults don't break the SAM rule
return s -> apply(s).trim();
}
}
Transformer upper = s -> s.toUpperCase(); // lambda implements the SAM
upper.apply(" hi "); // " HI "
The JDK is full of these: Runnable, Comparator, Function, Predicate, Supplier.
This is one capability abstract classes simply can't offer — you cannot write a lambda for
an abstract class. If you want your abstraction to be lambda-friendly, it has to be a
single-method interface.
Marker interfaces
A marker interface has no methods at all — it exists purely to tag a class so code
or the JVM can detect a capability at runtime via instanceof. The classic examples are
Serializable, Cloneable, and RandomAccess.
class Config implements Serializable { } // "instances of this class may be serialized"
Object o = new Config();
if (o instanceof Serializable) { /* the JVM checks exactly this before serializing */ }
Modern Java often prefers annotations for metadata, but marker interfaces still give you
two things annotations don't: a real type you can use in method signatures and a
compile-time check via instanceof. They're a niche but legitimate use of interfaces with
no abstract-class equivalent.
When to choose which
Decide by what you need to share:
- Reach for an interface when you're defining a capability that unrelated classes can have, when you need multiple inheritance of type, or when you want a lambda target. No shared state.
- Reach for an abstract class when you have a family of related classes that share fields, a constructor, or substantial implementation.
interface Comparable<T> { int compareTo(T o); } // any class can opt into being comparable
abstract class HttpServlet { // a family sharing request/response plumbing
protected void service() { /* shared template logic, calls abstract hooks */ }
}
In practice they combine beautifully: declare the public API as an interface, then offer
an optional AbstractXxx skeletal implementation for implementers who want the shared
plumbing — exactly the List / AbstractList pattern in the JDK. The default heuristic:
start with an interface, and only promote to (or add) an abstract class when you must
share state or a real chunk of implementation.
Recap
An abstract class is a half-built base for a tight family of related types — it can hold
state, a constructor, and a mix of concrete and abstract methods, but a class
extends only one. An interface is a contract any class can sign: it allows
default/static/private methods and public static final constants, but no
instance state and no constructors, and a class implements many — giving Java safe
multiple inheritance of type and behavior. Conflicting defaults force an override
(Interface.super.method()), and class beats interface when they collide. Remember the
specialized interface roles — functional interfaces for lambdas (SAM +
@FunctionalInterface) and marker interfaces for instanceof tagging. Default to an
interface; reach for an abstract class only when shared state or implementation demands it,
and combine them via skeletal AbstractXxx classes when you want both.