Default Methods
Before Java 8, every method declared in an interface was implicitly abstract — any class that implemented the interface had to provide a body for every single method. Default methods changed that rule: you can now write a method directly inside an interface, complete with an implementation, and any implementing class gets it for free.
Why Default Methods Exist
Imagine you maintain a library that ships a popular Collection interface used by thousands of classes. One day you need to add a forEach() method. Without default methods you’d have only bad options:
- Add the abstract method → every existing implementation breaks immediately.
- Create a new interface → all callers must migrate.
Default methods gave the Java team a clean escape hatch. When Java 8 added forEach(), stream(), and spliterator() to java.util.Collection, existing code compiled and ran without a single change. That backward-compatibility story is the main reason default methods exist.
Note: Default methods are sometimes called defender methods or virtual extension methods in the Java specification.
Basic Syntax
Use the default keyword before the return type, inside the interface body:
public interface Greeter {
// Abstract — must be implemented by every class
String getName();
// Default — has a body; implementing classes inherit this for free
default void greet() {
System.out.println("Hello, " + getName() + "!");
}
}
Any class that implements Greeter can call greet() without overriding it:
public class EnglishGreeter implements Greeter {
@Override
public String getName() {
return "Alice";
}
// greet() is inherited — no need to write it
}
public class Main {
public static void main(String[] args) {
Greeter g = new EnglishGreeter();
g.greet();
}
}
Output:
Hello, Alice!
Overriding a Default Method
A class is free to override a default method just like any other inherited method:
public class FormalGreeter implements Greeter {
@Override
public String getName() {
return "Dr. Smith";
}
@Override
public void greet() {
System.out.println("Good day, " + getName() + ". How do you do?");
}
}
Output:
Good day, Dr. Smith. How do you do?
Tip: Override a default method whenever the general behavior doesn’t fit your class — the whole point is flexibility, not forced uniformity.
Calling the Interface’s Default from the Override
If you override a default method but still want the original logic, use InterfaceName.super.methodName():
public class VerboseGreeter implements Greeter {
@Override
public String getName() {
return "Bob";
}
@Override
public void greet() {
System.out.println("[LOG] Greeting initiated");
Greeter.super.greet(); // delegate to the interface default
System.out.println("[LOG] Greeting complete");
}
}
Output:
[LOG] Greeting initiated
Hello, Bob!
[LOG] Greeting complete
Default Methods and Multiple Interfaces
A class can implement several interfaces, and that is where things get interesting. If two interfaces declare a default method with the same signature, the compiler forces you to resolve the conflict explicitly:
interface A {
default String hello() { return "Hello from A"; }
}
interface B {
default String hello() { return "Hello from B"; }
}
// Compiler error without the override below
public class C implements A, B {
@Override
public String hello() {
// Pick one, combine both, or write your own
return A.super.hello() + " & " + B.super.hello();
}
}
public class ConflictDemo {
public static void main(String[] args) {
System.out.println(new C().hello());
}
}
Output:
Hello from A & Hello from B
Warning: If you implement two interfaces that have conflicting default methods and you do not override the method in your class, the code will not compile. The compiler will never silently pick one over the other.
Interface Inheritance of Default Methods
Interfaces can extend other interfaces. A child interface may:
- Inherit the default method unchanged.
- Override it with another default implementation.
- Re-declare it as abstract, forcing implementing classes to provide a body.
interface Shape {
double area();
default String describe() {
return "I am a shape with area " + area();
}
}
interface Circle extends Shape {
// Re-declares describe() as abstract — implementing classes must override it
@Override
String describe();
}
// Now any class implementing Circle MUST provide describe()
public class RedCircle implements Circle {
private final double radius;
public RedCircle(double radius) { this.radius = radius; }
@Override
public double area() { return Math.PI * radius * radius; }
@Override
public String describe() {
return "Red circle, area = " + String.format("%.2f", area());
}
}
Default Methods vs Abstract Classes
Both can provide concrete methods. Here is a quick comparison to help you decide:
| Feature | Interface + default | Abstract class |
|---|---|---|
| Can hold state (fields) | No (only constants) | Yes |
| Constructor | No | Yes |
| Multiple inheritance | Yes — implement many interfaces | No — extend only one class |
| Access modifiers on methods | public only (until Java 9) | Any |
| When to use | Define a contract + convenience helpers | Shared state + partial implementation |
Tip: If you need instance fields or constructors, reach for an abstract class. If you just need to add behavior to an existing contract without breaking callers, a default method is the right tool.
Real-World Example — Comparable-Style Interface
import java.util.List;
public interface Sortable<T extends Comparable<T>> {
List<T> getItems();
default T findMin() {
return getItems().stream()
.min(Comparable::compareTo)
.orElseThrow(() -> new IllegalStateException("Empty list"));
}
default T findMax() {
return getItems().stream()
.max(Comparable::compareTo)
.orElseThrow(() -> new IllegalStateException("Empty list"));
}
}
public class NumberBag implements Sortable<Integer> {
private final List<Integer> numbers;
public NumberBag(List<Integer> numbers) {
this.numbers = numbers;
}
@Override
public List<Integer> getItems() { return numbers; }
}
public class SortableDemo {
public static void main(String[] args) {
NumberBag bag = new NumberBag(List.of(5, 2, 9, 1, 7));
System.out.println("Min: " + bag.findMin());
System.out.println("Max: " + bag.findMax());
}
}
Output:
Min: 1
Max: 9
NumberBag only needs to supply getItems() — the useful findMin() and findMax() come for free from the interface. This pattern keeps implementing classes thin.
Under the Hood
Bytecode representation
A default method is stored in the interface’s .class file just like a class method — it has a full Code attribute in the bytecode. At the call site the JVM uses a special invokeinterface instruction (same as abstract interface calls); it does not use invokevirtual.
vtable and itable
The JVM maintains an itable (interface method table) for each class. When a class inherits a default method without overriding it, the JVM links the itable slot directly to the interface’s method body. When a class does override it, the itable slot points to the class’s version instead. Either way, dynamic dispatch works at runtime with negligible overhead compared to regular virtual calls.
No diamond problem
Java avoids the C++ diamond problem because interfaces cannot carry state. If two interfaces both provide a default foo(), the worst outcome is a compile-time conflict that you must resolve explicitly — the JVM never has to guess which copy of shared mutable state to use. See vtable & Dynamic Dispatch for deeper detail on how the JVM resolves method calls.
Private interface methods (Java 9+)
Java 9 added private and private static methods to interfaces. These are helper methods you can factor out of long default methods — they are invisible to implementing classes and other interfaces.
public interface Logger {
default void logInfo(String msg) { log("INFO", msg); }
default void logError(String msg) { log("ERROR", msg); }
// private helper — only visible inside this interface
private void log(String level, String msg) {
System.out.println("[" + level + "] " + msg);
}
}
Common Pitfalls
- Treating default methods like abstract methods — if you forget the
defaultkeyword, the method is abstract and all implementing classes must provide a body. - Assuming default methods can access fields — they cannot, because interfaces have no instance fields. They can only call other interface methods or use their parameters.
- Ignoring conflict resolution — two interfaces with the same default method signature always requires an explicit override in the implementing class.
Related Topics
- Interfaces — the foundation that default methods build on; covers abstract method rules and interface design
- Abstract Class vs Interface — when to choose a default method over an abstract class
- Functional Interfaces — single-abstract-method interfaces that pair with lambdas; default methods keep them SAM-compliant
- Lambda Expressions — Java 8 feature that works hand-in-hand with interface evolution
- Method Overriding — the same mechanics apply when a class overrides a default method
- vtable & Dynamic Dispatch — how the JVM resolves which method body to execute at runtime