Topic 5.8
Default, Static and Private Interface Methods
In one line
Since Java 8 an interface can contain default methods (with a body that implementers inherit) and static helper methods; since Java 9 it can also have private methods shared by those bodies. Default methods let old interfaces grow without breaking existing classes.
Think of it like this
A phone that gets a software update. Your phone maker adds a new 'dark mode' button to every phone through an update. Old apps keep working, and they all get a reasonable dark mode for free. If an app wants a better one, it can provide its own. Default methods are that update: new behaviour added to an interface, with a sensible version built in, that implementers can override.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Default method
- An interface method with a body, marked
default. Implementing classes get it automatically and may override it. - Static interface method
- A method with a body, marked
static, that belongs to the interface. Call it through the interface name. - Private interface method
- A helper method inside an interface (Java 9+), usable only by the interface's own default, static and private methods.
- Interface evolution
- Adding new methods to an interface that is already in use without breaking existing implementations.
- Binary compatibility
- Old compiled
.classfiles keep working with a new version of a library, without being recompiled. - X.super.m()
- Syntax to call interface
X's defaultm()from a class or interface that directly implements or extendsX. - Diamond problem
- When a class gets the same method from two parents and it's unclear which one to use.
Step by step
01The problem defaults solve
Imagine you published interface Shape { double area(); } and a hundred teams implemented it. Now you want every shape to have describe(). Adding String describe(); as abstract would break all hundred classes: they no longer compile, and old compiled versions throw AbstractMethodError when someone calls the new method.
A default method gives the new method a body in the interface itself. Existing classes inherit it and keep working; classes that want something better override it.
interface Shape {
double area();
default String describe() { // new in version 2, breaks nobody
return getClass().getSimpleName() + " with area " + format(area());
}
static String format(double v) { // a helper that belongs to Shape
return String.format("%.1f", v);
}
}02Overriding a default and calling the original
An implementing class overrides a default like any method, with public. To reuse the interface's version inside the override, write Shape.super.describe(). This is the interface version of super.method() from Topic 5.2.
Shape.super.x() is only allowed when Shape is a direct superinterface of the class, and only if no other direct parent overrides that method more specifically.
03Three rules for conflicts
Rule 1: classes win. If the class or any superclass declares the method (concretely or abstractly), interface defaults are ignored. This keeps old class hierarchies behaving the same when interfaces gain defaults.
Rule 2: the most specific interface wins. If ProCamera extends Camera and both define start(), a class implementing both gets ProCamera's.
Rule 3: otherwise, you must choose. If Camera and Phone are unrelated and both have a default start(), the class must override start(). Inside, it can call Camera.super.start(), Phone.super.start(), both, or neither.
04The compile error for a real diamond
Here both interfaces give a default hello() and the class doesn't choose. The compiler refuses, naming both interfaces.
05Static methods: called through the interface only
List.of(...), Map.entry(...), Comparator.comparing(...) and Stream.of(...) are static interface methods. They act as factories that live next to the type they create, instead of in a separate utility class like Collections.
They are not inherited. If Impl implements Util, then Impl.twice(2) and new Impl().twice(2) fail to compile; only Util.twice(2) works. This was a deliberate choice so that adding a static method to an interface can never clash with a method in an implementing class.
06Private methods (Java 9) keep helpers private
Two default methods often share some logic. In Java 8 the only way to share it was another default method, which became part of the public API whether you liked it or not. Java 9 allows private (and private static) methods in interfaces.
They're visible only inside the interface body: implementing classes can't call or override them.
interface Report {
String title();
default String asText() { return frame(title().toUpperCase()); }
default String asMarkdown() { return frame("# " + title()); }
private String frame(String body) { // Java 9+: hidden helper
return "[" + body + "]";
}
}07What defaults still can't do
No state. A default can't declare instance fields, so it can only work through other methods (area(), title()). That's why defaults are usually short and built on the abstract methods.
No Object methods. default String toString() is a compile error: default method toString in interface Named overrides a member of java.lang.Object. Rule 1 says the class wins, and every class inherits toString from Object, so such a default could never run.
No final. You can't stop implementers overriding a default. If a step must not change, an abstract class with a final template method (Topic 5.6) is the tool.
Try it yourself
- 1
Remove the override in SmartPhone
In the conflict-rules example, delete SmartPhone's
start()method and compile. Read the 'unrelated defaults' error. Then make it return onlyPhone.super.start()and run. - 2
Try calling a private helper from outside
In the Report example, add
String test() { return frame("x"); }toSalesReport. The compiler sayscannot find symbol: the private method is invisible outside the interface. - 3
Try a default toString
Add
default String toString() { return title(); }toReportand compile. Read the error and explain it using rule 1.
Code & diagrams
Expected output
Square with area 4.0
Round! Circle with area 3.1
Shape.format(2.345) = 2.3Expected output
camera ready + phone ready
device ready (class wins)
pro camera ready (more specific wins)Expected output
[SALES Q3]
[# Sales Q3]
Sales Q3 (12 pages)
Office closed Friday (1 page)Expected output
[Asha, Ravi, Zoya]
[Zoya, Ravi, Asha]
mango stock: 0
List.sort is default: true
List.size is default: falseBreak it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Inherit two unrelated defaults
Implement two interfaces that both have a default hello() and don't override it.
interface A { default String hello() { return "A"; } }
interface B { default String hello() { return "B"; } }
class C implements A, B { }Break #2
Call a static interface method through an implementation
Call twice on an object of a class that implements Util.
interface Util { static int twice(int x) { return 2 * x; } }
class Impl implements Util { }
public class Main { public static void main(String[] a) { System.out.println(new Impl().twice(2)); } }Break #3
Override toString in an interface
Add default String toString() to an interface.
interface Named { default String toString() { return "x"; } }Myth vs fact
Myth
Default methods give Java full multiple inheritance like C++.
Fact
They give multiple inheritance of *behaviour* only. Interfaces still have no instance state, and conflicts must be resolved explicitly, so the hard parts of C++ multiple inheritance (duplicated state) don't arise.
Myth
Static methods in interfaces are inherited like static methods in classes.
Fact
They aren't. Only InterfaceName.method() works.
Myth
Private interface methods arrived with default methods in Java 8.
Fact
Java 8 brought default and static methods. Private interface methods came in Java 9.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
Bytecode: a default method is a normal non-abstract method in the interface's class file (version 52+). Callers still use
invokeinterface(orinvokevirtualthrough a class type); HotSpot fills the implementing class's vtable/itable entries with the default when no class provides one.X.super.m()compiles toinvokespecial X.m(and requires X to be a direct superinterface). - ▸
Defaults are not fully binary safe. If a class implements A and B compiled separately, and both later gain a default
m(), the old class file isn't recompiled to catch the conflict. Callingm()then throwsIncompatibleClassChangeErrorat run time, because method resolution finds two maximally specific defaults. - ▸
Rule 1 ('class wins') applies even to abstract class methods: if a superclass re-declares
m()as abstract, the interface's default is not inherited and the concrete subclass must implementm(). That's how a framework can force subclasses to override something an interface made optional. - ▸
Defaults are a common place for subtle concurrency bugs:
Map.computeIfAbsent's default implementation is not atomic.ConcurrentHashMapoverrides it with an atomic version; a custom thread-safeMapthat relies on the default is not safe (Topic 13.8).
Remember this
- 1
A default method is an interface method with a body, marked
default:default String describe() { return "area " + area(); }. Every implementing class inherits it automatically and may override it. A default method can call the interface's abstract methods, because the object it runs on is always a real implementation. - 2
Default methods exist mainly for interface evolution. Before Java 8, adding a method to
Collectionwould have broken every collection class ever written. Java 8 addedIterable.forEach,Collection.stream,Collection.removeIf,List.sortandMap.getOrDefaultas defaults, so all existing classes got them for free. - 3
If a class inherits the same default method from two places, three rules decide: (1) a class wins: a method from the superclass chain beats any interface default; (2) the more specific interface wins: if
B extends Aand both define it, B's version is used; (3) otherwise it's a conflict and the class must override the method, optionally calling one parent withA.super.method(). - 4
A static interface method belongs to the interface itself: call it as
Comparator.naturalOrder()orList.of(1, 2). Unlike static methods in classes, it is not inherited by implementing classes or sub-interfaces, somyComparator.naturalOrder()doesn't compile. - 5
A private interface method (Java 9) is a helper visible only inside the interface. Use it to share code between default methods without exposing that code as part of the public contract.
private staticmethods are allowed too, for helpers used by static methods. - 6
Defaults still can't hold state (no instance fields), can't be
finalorsynchronized, and can't overrideequals,hashCodeortoStringfrom Object (rule 1 means Object's version would always win, so the compiler forbids it).
Explain it without notes
Why were default methods added in Java 8?
State the three rules for resolving a method inherited from several places.
How do static interface methods differ from static class methods?
What problem do private interface methods solve?
Why can't a default method override toString()?
Practice
Create interface Greeter { String name(); default String greet() { ... } } that returns "Hello, <name>!". Implement it in English and in Hindi, where Hindi overrides greet() to return "Namaste, <name>!".
Create two interfaces Walker and Swimmer, each with a default move(). Write Frog implements Walker, Swimmer whose move() returns both results joined by " and ".
Write an interface Temperature with abstract double celsius(), a default fahrenheit(), and a static factory Temperature of(double c) that returns an object of a small implementing class in the same file.
Trade-offs
- ↔
Defaults let you add behaviour to an interface cheaply, but each one becomes part of every implementer's API forever, and implementers inherit behaviour they never reviewed. Add defaults that are obviously correct for every implementation, or document them with
@implSpec. - ↔
Static factories on interfaces (
List.of) keep creation next to the type, but they can only return types visible to the interface. Hidden implementation classes need to be package-private classes or private nested classes.
Done when you can
Done when you can add a default method to an interface and override it in one implementation.
Done when you can apply the three conflict rules and use
X.super.m().Done when you can call static interface methods correctly and know they aren't inherited.
Done when you can use private interface methods (Java 9) to share code between defaults.
Done when you can name JDK default methods like
List.sortandMap.getOrDefault.