Static Interface Methods
Static interface methods, introduced in Java 8 alongside default methods, are methods declared static with a body inside an interface, callable only through the interface name and never inherited by implementing classes or invoked through an instance. They give interfaces a natural home for factory methods, validation utilities, and helper logic that conceptually belongs with the interface's contract but doesn't operate on an instance, eliminating the older pattern of pairing every interface with a same-named *Utils or *Factory companion class (e.g. Collection/Collections, Path/Paths). This entry covers the motivation and the Collections/Collections-style problem it replaces, syntax and invocation rules, how static methods differ from default methods in inheritance and dispatch, interaction with private static helper methods, and common factory/utility design patterns built on this feature.
Motivation — Replacing the Utility-Class Companion Pattern
// ── Before Java 8: interface + separate utility-class pairing ──────────
interface Shape {
double area();
}
// Forced into a separate, unrelated-looking class:
final class Shapes {
private Shapes() {}
static Shape circle(double r) { return () -> Math.PI * r * r; }
static Shape square(double s) { return () -> s * s; }
}
Shape c = Shapes.circle(5); // discoverability relies on knowing "Shapes" exists
// ── Java 8+: static factory methods live directly on the interface ─────
interface Shape2 {
double area();
static Shape2 circle(double r) { return () -> Math.PI * r * r; }
static Shape2 square(double s) { return () -> s * s; }
}
Shape2 c2 = Shape2.circle(5); // IDE autocomplete on "Shape2." reveals factories directly
// ── Real JDK examples ───────────────────────────────────────────────────
Comparator<String> byLen = Comparator.comparingInt(String::length);
Function<String, String> id = Function.identity();
Predicate<String> notBlank = Predicate.not(String::isBlank);
List<Integer> nums = List.of(1, 2, 3); // List's static factory
Optional<String> opt = Optional.of("value"); // Optional's static factorySyntax, Invocation Rules, and Dispatch
interface Repo {
static Repo inMemory() { return new InMemoryRepo(); }
}
class InMemoryRepo implements Repo {
// InMemoryRepo.inMemory() does NOT exist — static methods are not inherited
}
Repo r = Repo.inMemory(); // OK — called through the interface
// InMemoryRepo.inMemory(); // COMPILE ERROR — not inherited by the implementor
// ── Calling through an instance is a compile error for interfaces ──────
Repo instance = Repo.inMemory();
// instance.inMemory(); // COMPILE ERROR: static method called on instance reference
// ── No override relationship, even with identical signatures ──────────
interface Factory {
static String create() { return "from interface"; }
}
class Maker implements Factory {
static String create() { return "from class"; } // unrelated, separate method
}
System.out.println(Factory.create()); // "from interface"
System.out.println(Maker.create()); // "from class"
// Resolution is purely by which type name precedes the call — no polymorphism involvedStatic Methods vs Default Methods, and Private Static Helpers
interface Temperature {
double celsius();
// Default method — needs an instance (this) to operate on:
default double toFahrenheit() {
return celsius() * 9.0 / 5.0 + 32;
}
// Static method — constructs a new instance, no "this" involved:
static Temperature ofCelsius(double c) {
validate(c);
return () -> c;
}
static Temperature ofFahrenheit(double f) {
return ofCelsius((f - 32) * 5.0 / 9.0);
}
// Private static helper — shared validation logic, not part of public API:
private static void validate(double c) {
if (c < -273.15) {
throw new IllegalArgumentException("Below absolute zero: " + c);
}
}
}
Temperature t1 = Temperature.ofCelsius(100);
Temperature t2 = Temperature.ofFahrenheit(32);
System.out.println(t1.toFahrenheit()); // 212.0
System.out.println(t2.celsius()); // 0.0
// Temperature.validate(-300); // COMPILE ERROR — private to the interface