Command Palette

Search for a command to run...

PHASE 5Intermediate Java 8+ ~31 min· topic 8 of 13

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 .class files keep working with a new version of a library, without being recompiled.
X.super.m()
Syntax to call interface X's default m() from a class or interface that directly implements or extends X.
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.

Main.javawhole filejava
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.

Three rules for conflictsdiagram
Rendering diagram…

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.

terminal
$ javac Main.java
── expected output ──
Main.java:3: error: types A and B are incompatible;
class C implements A, B { }
^
class C inherits unrelated defaults for hello() from types A and B
1 error

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.

terminal
$ javac Main.java
── expected output ──
Main.java:3: error: cannot find symbol
public class Main { public static void main(String[] a) { System.out.println(new Impl().twice(2)); } }
^
symbol: method twice(int)
location: class Impl
1 error

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.

Main.javawhole filejava
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. 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 only Phone.super.start() and run.

  2. 2

    Try calling a private helper from outside

    In the Report example, add String test() { return frame("x"); } to SalesReport. The compiler says cannot find symbol: the private method is invisible outside the interface.

  3. 3

    Try a default toString

    Add default String toString() { return title(); } to Report and compile. Read the error and explain it using rule 1.

Code & diagrams

Default and static methods in one interface New tab
Sign in to run this example in your browser.

Expected output

Square with area 4.0
Round! Circle with area 3.1
Shape.format(2.345) = 2.3
The three conflict rules in action New tab
Sign in to run this example in your browser.

Expected output

camera ready + phone ready
device ready (class wins)
pro camera ready (more specific wins)
Private interface methods Java 9+ New tab
Sign in to run this example in your browser.

Expected output

[SALES Q3]
[# Sales Q3]
Sales Q3 (12 pages)
Office closed Friday (1 page)
Default and static methods you already use in the JDK New tab
Sign in to run this example in your browser.

Expected output

[Asha, Ravi, Zoya]
[Zoya, Ravi, Asha]
mango stock: 0
List.sort is default: true
List.size is default: false

Break 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.

Main.javawhole filejava
interface A { default String hello() { return "A"; } }
interface B { default String hello() { return "B"; } }
class C implements A, B { }
terminal
$ javac Main.java
── what you'll see ──
Main.java:3: error: types A and B are incompatible;
class C implements A, B { }
^
class C inherits unrelated defaults for hello() from types A and B
1 error

Break #2

Call a static interface method through an implementation

Call twice on an object of a class that implements Util.

Main.javawhole filejava
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)); } }
terminal
$ javac Main.java
── what you'll see ──
Main.java:3: error: cannot find symbol
public class Main { public static void main(String[] a) { System.out.println(new Impl().twice(2)); } }
^
symbol: method twice(int)
location: class Impl
1 error

Break #3

Override toString in an interface

Add default String toString() to an interface.

Main.javawhole filejava
interface Named { default String toString() { return "x"; } }
terminal
$ javac Main.java
── what you'll see ──
Main.java:1: error: default method toString in interface Named overrides a member of java.lang.Object
interface Named { default String toString() { return "x"; } }
^
1 error

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 (or invokevirtual through a class type); HotSpot fills the implementing class's vtable/itable entries with the default when no class provides one. X.super.m() compiles to invokespecial 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. Calling m() then throws IncompatibleClassChangeError at 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 implement m(). 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. ConcurrentHashMap overrides it with an atomic version; a custom thread-safe Map that relies on the default is not safe (Topic 13.8).

Remember this

  1. 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. 2

    Default methods exist mainly for interface evolution. Before Java 8, adding a method to Collection would have broken every collection class ever written. Java 8 added Iterable.forEach, Collection.stream, Collection.removeIf, List.sort and Map.getOrDefault as defaults, so all existing classes got them for free.

  3. 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 A and 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 with A.super.method().

  4. 4

    A static interface method belongs to the interface itself: call it as Comparator.naturalOrder() or List.of(1, 2). Unlike static methods in classes, it is not inherited by implementing classes or sub-interfaces, so myComparator.naturalOrder() doesn't compile.

  5. 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 static methods are allowed too, for helpers used by static methods.

  6. 6

    Defaults still can't hold state (no instance fields), can't be final or synchronized, and can't override equals, hashCode or toString from Object (rule 1 means Object's version would always win, so the compiler forbids it).

Explain it without notes

01

Why were default methods added in Java 8?

02

State the three rules for resolving a method inherited from several places.

03

How do static interface methods differ from static class methods?

04

What problem do private interface methods solve?

05

Why can't a default method override toString()?

Practice

01

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>!".

02

Create two interfaces Walker and Swimmer, each with a default move(). Write Frog implements Walker, Swimmer whose move() returns both results joined by " and ".

03

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.sort and Map.getOrDefault.