W3docs

Java Interface Default and Static Methods

Add default and static methods to Java interfaces to evolve APIs without breaking existing implementations.

Up to Java 7, an interface could only declare abstract methods — bodies lived in implementing classes. Java 8 added three new things you can put inside an interface:

  • default methods — a method with a body, inherited by implementors that don't override it.
  • static methods — utility methods owned by the interface itself.
  • private methods (Java 9) — helpers shared between the interface's default and static methods.

The motivating use case was API evolution. Once java.util.Collection had been around for fifteen years with millions of implementations, the Java team wanted to add stream() without breaking every one of them. default Stream<E> stream() { ... } did exactly that.

Default methods

A default method has a body. Implementing classes inherit it for free and can override it if they want different behavior:

public interface Greeter {
  String name();

  default String greet() {
    return "Hello, " + name() + "!";
  }
}

public class English implements Greeter {
  public String name() { return "Alice"; }
}

public class Loud implements Greeter {
  public String name()  { return "Bob"; }
  public String greet() { return "HEY " + name().toUpperCase() + "!"; }   // override
}

new English().greet();   // Hello, Alice!
new Loud().greet();      // HEY BOB!

default is just a marker — the keyword tells the compiler "this is a method body inside an interface." Without it, an interface method has no body.

Adding a default method to an existing interface is a non-breaking change. Implementors that don't override it inherit the default. That's the property that makes interface evolution possible.

Static methods on interfaces

A static method on an interface belongs to the interface, not to instances. Call it through the interface name:

public interface Path {
  static Path of(String s) { return new SimplePath(s); }
  String value();
}

Path p = Path.of("/tmp/foo");

A common use is factory methods that produce instances of the interface — Path.of, List.of, Map.of, Stream.of. They let callers depend on the interface even when constructing, instead of having to pick a specific implementation class.

Static interface methods are not inherited. You always call them through the interface name, never through a subclass.

Private methods (Java 9+)

A private method on an interface is a helper visible only to other methods on the same interface. It lets two default methods share logic without exposing that logic to implementors:

public interface Logger {
  default void info(String msg) { log("INFO", msg); }
  default void warn(String msg) { log("WARN", msg); }

  private void log(String level, String msg) {
    System.out.println("[" + level + "] " + msg);
  }
}

Without private, you'd have to either copy the body into both default methods or expose log as a default method itself — which would let implementors override it, which you probably don't want.

What default methods cannot do

default methods live on the interface, which has no state. They can call abstract methods and other default/static methods, but they can't read or write instance fields — there are none to read.

public interface Counter {
  int get();
  void increment();

  default void incrementTwice() {     // ok — only calls abstract methods
    increment();
    increment();
  }
}

This limits how much real behavior a default method can carry. They're best for thin convenience layers over the abstract methods, not for replacing the implementation entirely.

Diamond conflicts

When a class implements two interfaces that both supply a default method with the same signature, the class must explicitly resolve the conflict:

interface A { default String hello() { return "A"; } }
interface B { default String hello() { return "B"; } }

class C implements A, B {
  @Override
  public String hello() {
    return A.super.hello() + B.super.hello();   // explicit pick
  }
}

A.super.hello() invokes A's default; B.super.hello() invokes B's. The compiler refuses to compile class C implements A, B { } without an override — there's no automatic winner. This is Java's answer to the multiple-inheritance "diamond problem" — when it appears, the class has to make the call.

A subclass also wins over an inherited default: if a concrete superclass provides hello(), that wins over any default method from an interface.

When to add a default method

default is the right tool when:

  • You want to extend an existing, widely-implemented interface without breaking implementors.
  • The new method is genuinely derivable from the existing abstract methods.
  • The default is "obviously correct" — most implementors will be happy with it.

It's the wrong tool when:

  • You're tempted to put real shared state-based behavior in there (use an abstract class instead).
  • The default isn't actually useful and implementors will all override it anyway (just leave it abstract).

A worked example

java— editable, runs on the server

What's next

Interfaces and abstract classes are about how types relate between files. The next sub-topic — nested classes — is about how types relate inside a single file: classes declared inside other classes, and the four flavors Java offers for them. Continue to nested classes.

Practice

Practice

What is the main practical benefit of marking a new interface method default rather than leaving it abstract?