Why this matters
- This is the most frequently asked design question in Java interviews, and default methods in Java 8 changed the correct answer.
- Choosing wrongly shows up later as a class that cannot extend the thing it needs to, because its one
extendsslot is already taken. - Every modern Java API — streams, collections, Spring, JDBC — is defined in interfaces, so reading those APIs requires fluency here.
Interfaces
An interface is a contract. Before Java 8 it could hold nothing but abstract method signatures and constants; three releases have since widened it.
public interface Repository<T, ID> {
// Abstract: implicitly public abstract
Optional<T> findById(ID id);
List<T> findAll();
void save(T entity);
// Default: a body, inheritable and overridable — added in Java 8
default boolean existsById(ID id) {
return findById(id).isPresent();
}
// Static: a utility bound to the interface, not inherited — added in Java 8
static <T, ID> Repository<T, ID> empty() {
return new EmptyRepository<>();
}
// Private: shared helper for default methods, hidden from implementers — added in Java 9
private void auditLookup(ID id) { }
// Constant: implicitly public static final
int MAX_BATCH_SIZE = 500;
}
Member rules, stated once
- Methods without a body are implicitly
public abstract. - Fields are implicitly
public static final— constants, never instance state. defaultmethods carry a body that implementers inherit and may override.staticmethods belong to the interface itself and are not inherited by implementers.privatemethods exist only to share code betweendefaultmethods in the same interface.- An interface has no constructor and can never be instantiated.
Abstract classes
An abstract class cannot be instantiated and may mix finished and unfinished methods. Crucially, it can hold state and a constructor.
public abstract class Shape {
private final String name; // real instance state
protected Shape(String name) { // constructor, called via super()
this.name = name;
}
public abstract double area(); // subclasses must supply this
public String describe() { // shared, concrete behaviour
return name + " with area " + String.format("%.2f", area());
}
}
public class Circle extends Shape {
private final double radius;
public Circle(double radius) {
super("Circle");
this.radius = radius;
}
@Override
public double area() {
return Math.PI * radius * radius;
}
}
Note what describe() does: it calls the abstract area(). The parent defines the algorithm and
the child supplies one step. That is the template method pattern, and it is the clearest reason to
prefer an abstract class.
Choosing between them
| Aspect | Interface | Abstract class |
|---|---|---|
| How many per class | Any number | Exactly one |
| Instance fields | No — constants only | Yes |
| Constructor | No | Yes |
| Method bodies | default, static, private | Any concrete method |
| Access modifiers on members | public, or private for helpers | Any, including protected |
| Expresses | A capability — can-do | An identity — is-a |
| Adding a method later | Safe if you give it a default | Safe if you give it a body |
How many per class
InterfaceAny numberAbstract classExactly oneInstance fields
InterfaceNo — constants onlyAbstract classYesConstructor
InterfaceNoAbstract classYesMethod bodies
Interfacedefault, static, privateAbstract classAny concrete methodAccess modifiers on members
Interfacepublic, or private for helpersAbstract classAny, including protectedExpresses
InterfaceA capability — can-doAbstract classAn identity — is-aAdding a method later
InterfaceSafe if you give it a defaultAbstract classSafe if you give it a body
The single-inheritance limit is usually the deciding factor.
A practical rule
- Need to declare a capability that unrelated types might have? Interface.
Comparable,Runnable,AutoCloseableare all capabilities. - Need shared state or a constructor? Abstract class, because an interface cannot hold either.
- Want to define an algorithm with holes for subclasses to fill? Abstract class, using the template method shape above.
- Unsure? Start with an interface. It leaves the subclass's one
extendsslot free, and you can add an abstract base class later if duplication appears.
Both together is a common and good pattern: a public interface for callers, plus an abstract
skeleton implementation for implementers. The JDK does this throughout — List with
AbstractList, Map with AbstractMap.
When two interfaces collide
Default methods reintroduced a limited diamond, and the language forces you to resolve it.
interface Flyer {
default String move() { return "flying"; }
}
interface Swimmer {
default String move() { return "swimming"; }
}
class Duck implements Flyer, Swimmer {
@Override
public String move() {
return Flyer.super.move() + " and " + Swimmer.super.move();
}
}
Without the override this would not compile. The Interface.super.method() syntax is the only way
to name a specific inherited default.
Functional interfaces
An interface with exactly one abstract method can be implemented by a lambda. default and
static methods do not count towards the one.
@FunctionalInterface
public interface Validator<T> {
boolean test(T value); // the single abstract method
default Validator<T> and(Validator<T> other) {
return value -> test(value) && other.test(value);
}
}
Validator<String> notBlank = text -> !text.isBlank();
Validator<String> shortEnough = text -> text.length() <= 50;
Validator<String> rule = notBlank.and(shortEnough);
System.out.println(rule.test("Core Java")); // true
The @FunctionalInterface annotation is optional but worth adding: it makes the compiler reject a
second abstract method, protecting every lambda that depends on the shape.
Marker interfaces and annotations
A marker interface has no members and exists purely to tag a type — Serializable and
Cloneable are the well-known examples. Code then tests the tag with instanceof.
Annotations largely replaced them, because an annotation can carry parameters and apply to methods and fields as well as types.
@Deprecated(since = "3.2", forRemoval = true)
public void oldApi() { }
@FunctionalInterface
public interface Transformer<T, R> {
R apply(T input);
}
Built-in annotations worth knowing
@Override— compile-time check that you really are overriding.@Deprecated— warns callers, andforRemoval = truesays the removal is planned.@FunctionalInterface— enforces exactly one abstract method.@SuppressWarnings— silences a specific warning in the narrowest possible scope.@SafeVarargs— asserts a generic varargs method does not leak an unsafe array.
Common misreadings
- "Interfaces cannot have method bodies." They can, since Java 8:
defaultandstatic, plusprivatehelpers since Java 9. - "Abstract classes must contain abstract methods." They need not. Declaring a class
abstractonly prevents instantiation. - "Interface fields are instance state." They are
public static finalconstants, shared and unmodifiable. - "An abstract class cannot have a constructor." It must, and subclasses call it through
super(). - "Interfaces are always the right default." When you need shared state, only an abstract class works.
- "
defaultmethods brought multiple inheritance of state." They brought behaviour only. Instance fields are still single-inheritance.
Quick recall
Everything you need if you only revisit this box.
- Interface = capability; abstract class = identity with shared implementation.
- An interface holds abstract,
default,staticandprivatemethods pluspublic static finalconstants — never instance state, never a constructor. - An abstract class can hold state, a constructor and
protectedmembers, and is the right tool for a template method. - A class implements many interfaces but extends one class. That limit usually decides the choice.
- Conflicting defaults must be resolved by overriding, using
Interface.super.method(). A concrete class method always beats a default. - One abstract method makes an interface functional and lambda-compatible;
@FunctionalInterfaceenforces it. - Annotations have largely replaced marker interfaces because they carry parameters and wider targets.
Test yourself
Answer these before moving on — recall is what makes it stick.