Default & Static Methods in Interfaces
Why default methods were added, the three resolution rules when a class inherits conflicting defaults, calling a specific supertype with X.super.method(), and private interface methods.
On this page
Default methods look like a convenience feature and were actually a compatibility mechanism. The
Java 8 library designers needed to add stream() to Collection, and there was no way to do it
without breaking the world.
Key Takeaways
- Default methods exist for interface evolution: adding a method to a published interface without breaking implementers.
- Resolution order: class wins over interface, then most specific interface, then the class must override.
Interface.super.method()calls a specific supertype's default explicitly.- Interfaces gained behaviour but still cannot hold instance state — that is why this is not full multiple inheritance.
- Since Java 9, interfaces can have private methods to share code between defaults.
The problem they solved
public interface Collection<E> extends Iterable<E> {
// ... every method that existed in Java 7 ...
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
default boolean removeIf(Predicate<? super E> filter) {
boolean removed = false;
Iterator<E> it = iterator();
while (it.hasNext()) {
if (filter.test(it.next())) { it.remove(); removed = true; }
}
return removed;
}
default Spliterator<E> spliterator() {
return Spliterators.spliterator(this, 0);
}
}Before Java 8, adding any of those would have broken every class implementing Collection — in the
JDK, in every framework, and in every application. Default methods made the addition source- and
binary-compatible: an existing implementation that knows nothing about stream() inherits a working
one.
They also enabled the functional interfaces to carry composition helpers. Predicate stays functional
— one abstract method, test — while providing and, or and negate as defaults, and not and
isEqual as statics.
Static methods on interfaces
public interface Validator<T> {
List<String> validate(T target); // the abstract method
static <T> Validator<T> alwaysValid() { // static: not inherited
return t -> List.of();
}
static <T> Validator<T> requireNonNull(String field, Function<T, ?> getter) {
return t -> getter.apply(t) == null ? List.of(field + " is required") : List.of();
}
}
Validator<Order> v = Validator.requireNonNull("customer", Order::customer);Static interface methods are not inherited — SomeImplementation.alwaysValid() does not compile.
They are called on the interface itself. Their purpose is to eliminate the companion utility class:
Comparator.comparing, List.of, Map.entry, Stream.of and Predicate.not all live on the
interface rather than in a Collections-style helper.
The diamond, and the three rules
interface Greeter {
default String greet() { return "hello from Greeter"; }
}
interface Shouter {
default String greet() { return "HELLO FROM SHOUTER"; }
}
// Does not compile:
// class Both implements Greeter, Shouter { }
// error: types Greeter and Shouter are incompatible;
// class Both inherits unrelated defaults for greet()
class Both implements Greeter, Shouter {
@Override
public String greet() {
// Explicitly choose, or combine. Interface.super.method() is the syntax.
return Greeter.super.greet() + " / " + Shouter.super.greet();
}
}The compiler refuses to pick for you. That refusal is the design: an ambiguity that resolves silently would be a source of bugs on every future library upgrade.
The full resolution algorithm has three steps, applied in order:
Rule 1 — the class wins. A concrete method inherited from a superclass beats any interface default, even if the interface is "closer" in the declaration:
class Base {
public String greet() { return "from Base"; }
}
class Derived extends Base implements Greeter { }
new Derived().greet(); // "from Base" — Greeter's default is not usedThis rule exists for compatibility: adding a default method to an interface must never change the behaviour of an existing class that already has that method.
Rule 2 — the most specific interface wins.
interface Loud extends Greeter {
@Override default String greet() { return "LOUD"; }
}
class Ok implements Greeter, Loud { } // compiles
new Ok().greet(); // "LOUD" — Loud is more specific than GreeterRule 3 — otherwise, override. As in the first example. And note that the override may re-abstract the method:
interface Strict extends Greeter {
@Override String greet(); // no body — forces implementers to provide one
}Private interface methods
Java 9 added private and private static methods to interfaces, closing an obvious gap: before
that, two default methods sharing logic had to either duplicate it or expose a public helper that
became part of the API forever.
public interface EventSink {
void write(String payload);
default void info(String message) { emit("INFO", message); }
default void error(String message) { emit("ERROR", message); }
// Private: shared by the defaults, invisible to implementers and callers.
private void emit(String level, String message) {
write("[" + level + "] " + Instant.now() + " " + message);
}
private static String truncate(String s, int max) {
return s.length() <= max ? s : s.substring(0, max) + "...";
}
}What they are still not
Interfaces cannot declare instance fields. Every field in an interface is implicitly
public static final, so there is no inherited state and therefore no diamond problem for state —
which is the hard version of multiple inheritance that Java deliberately avoided.
interface Counter {
int count = 0; // public static final — shared by ALL implementers
// private int count; // does not compile
default void increment() {
// count++; // does not compile: cannot assign a final field
}
}That single restriction is the whole answer to "does Java 8 have multiple inheritance?" — behaviour yes, state no.
Interfaces also cannot have constructors, cannot declare protected or package-private members, and
cannot override Object methods with a default. That last one is worth knowing: writing
default String toString() in an interface is a compile error, because Object's implementation
would win by rule 1 anyway and allowing the declaration would only mislead.
Abstract class or interface, revisited
With defaults in the picture, the decision reduces to one question — does it need instance state or a constructor?
| Need | Choose |
|---|---|
| Mutable or immutable instance fields | Abstract class |
Constructor logic, or super(...) chaining | Abstract class |
| A capability that unrelated types may also want | Interface |
| Multiple supertypes | Interface |
| A published API you expect to extend later | Interface with defaults |
The JDK uses both together deliberately: List is the interface everything is written against, and
AbstractList is a skeleton that turns twelve methods into two for anyone implementing it. Naming
that pairing is a good way to close the answer.
Interview shortlist
Expect: why default methods were added (interface evolution, and the Collection.stream() example);
what happens with two conflicting defaults and how to resolve it; whether this is multiple
inheritance; and what a static interface method is for. The strongest answer to the first one names
the concrete problem — stream() on Collection — rather than the abstract capability.
Frequently Asked Questions
Why were default methods added to Java 8?
How does Java resolve conflicting default methods?
Does this make Java support multiple inheritance?
Related tutorials
- Optional: Correct Use and Common AbuseWhat Optional was designed for and what it was not, the orElse versus orElseGet trap that evaluates the fallback every time, chaining with map and flatMap, and why Optional fields are a mistake.
- The java.time APIChoosing between Instant, LocalDateTime and ZonedDateTime, Period versus Duration, what happens at a daylight-saving gap, and how to store timestamps so they survive a zone change.
- Parallel Streams and the Common ForkJoinPoolWhy every parallel stream in your JVM shares one pool, which sources split well, the N times Q rule for deciding, and why a blocking call inside a parallel stream can stall the whole application.
- Modern Java 9-21: Records, Sealed Types, Pattern MatchingWhat actually changed after Java 8 and why it matters in an interview: var, records, sealed interfaces, pattern matching for switch, text blocks, the module system and virtual threads.