Interfaces

This section documents the current Java release line, with Java 25 LTS as the reference point (no specific patch version is pinned), as published at the Java developer portal, the Java Tutorials, and the Java SE API specification — which are the references these pages are written and verified against.

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

An interface is a reference type that specifies what a class can do without saying how. A class may implement many interfaces, so interfaces give Java multiple inheritance of type. This page follows the dev.java "Interfaces" track and Defining an Interface.

Declaring and Implementing Interfaces

An interface declares abstract methods (implicitly public abstract) that implementing classes must define. A class lists every interface it honours after implements, separated by commas. See Implementing an Interface.

interface Drawable {
    void draw();                     // implicitly public abstract
}

interface Resizable {
    void resize(double factor);
}

// one class, two interfaces: multiple inheritance of type
class Sprite implements Drawable, Resizable {

    private double scale = 1.0;

    @Override
    public void draw() {
        System.out.println("drawing at scale " + scale);
    }

    @Override
    public void resize(double factor) {
        scale *= factor;
    }
}

Drawable d = new Sprite();      // a Sprite is-a Drawable
Resizable r = (Resizable) d;    // ...and also a Resizable

A field declared in an interface is implicitly public static final — a constant, not instance state:

interface HttpStatus {
    int OK = 200;                 // public static final
    int NOT_FOUND = 404;
    int SERVER_ERROR = 500;
}

int code = HttpStatus.NOT_FOUND;   // referenced through the interface name

Modern code usually prefers an enum over a bag of int constants — see Enums. Interface constants remain common for genuinely primitive protocol values.

default, static, and private Interface Methods

A default method carries a body in the interface itself. It was added in Java 8 so a published interface could gain new methods without breaking every existing implementation — the classic API-evolution problem described in Evolving Interfaces and Default Methods.

interface Logger {
    void log(String level, String message);           // abstract: implementers provide this

    // added later; existing implementers keep compiling
    default void info(String message)  { log("INFO", message); }
    default void error(String message) { log("ERROR", message); }

    // static: a helper namespaced under the interface, not inherited
    static Logger toConsole() {
        return (level, message) -> System.out.println("[" + level + "] " + message);
    }

    // private: shared code for the default methods, hidden from implementers
    private String stamp(String message) {
        return java.time.Instant.now() + " " + message;
    }

    default void audit(String message) { log("AUDIT", stamp(message)); }
}

Logger console = Logger.toConsole();   // static factory
console.info("started");               // [INFO] started  -- default method
console.error("boom");                 // [ERROR] boom

static interface methods (Java 8) are called through the interface name and are not inherited by implementers. private interface methods (Java 9) let several default methods share implementation without exposing it as API.

Functional Interfaces

An interface with exactly one abstract method is a functional interface and can be implemented by a lambda or method reference. The optional @FunctionalInterface annotation makes the compiler enforce the "single abstract method" rule.

@FunctionalInterface
interface Transformer<T, R> {
    R apply(T input);

    default int arity() { return 1; }   // default methods do not count against the SAM rule
}

Transformer<String, Integer> length = String::length;   // method reference
Transformer<String, String> shout = s -> s.toUpperCase() + "!";

length.apply("hello");   // 5
shout.apply("hi");       // "HI!"

The java.util.function package supplies ready-made functional interfaces (Function, Predicate, Supplier, Consumer, BiFunction, …​); prefer them over hand-rolled ones. Full treatment is on Lambdas and Method References.

Comparable vs. Comparator

Comparable<T> defines a type’s single natural ordering via compareTo; the type implements it directly. Comparator<T> is a separate object describing some other ordering, passed in where it is needed.

record Person(String name, int age) implements Comparable<Person> {
    @Override
    public int compareTo(Person other) {
        return Integer.compare(this.age, other.age);   // natural order: by age
    }
}

var people = new java.util.ArrayList<>(java.util.List.of(
        new Person("Ada", 36),
        new Person("Bо", 36),
        new Person("Cy", 24)));

java.util.Collections.sort(people);   // uses compareTo: youngest first

compareTo (and Comparator.compare) must return a negative int, zero, or a positive int, and should be consistent with equals wherever practical. Build multi-key comparators with the Comparator factories rather than nested `if`s:

import java.util.Comparator;

Comparator<Person> byName       = Comparator.comparing(Person::name);
Comparator<Person> byAgeThenName = Comparator
        .comparingInt(Person::age)
        .thenComparing(Person::name);
Comparator<Person> oldestFirst  = Comparator.comparingInt(Person::age).reversed();
Comparator<Person> nullSafeName = Comparator.comparing(
        Person::name, Comparator.nullsLast(Comparator.naturalOrder()));

people.sort(byAgeThenName);
people.sort(oldestFirst);

comparing / comparingInt extract a sort key, thenComparing breaks ties, and reversed flips the whole order. These return new Comparator instances and can be chained freely.

default-Method Resolution Rules

When a class inherits a method with the same signature from more than one place, Java resolves it with three rules (JLS 9.4.1; dev.java covers this under "Interfaces"):

1. A class (superclass) wins over an interface. A concrete or inherited class method always beats any default.

interface Greeter {
    default String greet() { return "hi from interface"; }
}

class BaseGreeter {
    public String greet() { return "hi from class"; }
}

class Combined extends BaseGreeter implements Greeter { }

new Combined().greet();   // "hi from class"  -- the superclass method wins

2. The more specific interface wins. If one interface extends the other, its default overrides the parent’s — no conflict.

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

class C implements A, B { }

new C().id();   // "B"  -- B is more specific than A

3. Otherwise you must override and disambiguate with Interface.super.method(). Two unrelated interfaces each supplying a default of the same signature is the diamond case, and the class does not compile until it resolves it:

interface Walk { default String move() { return "walking"; } }
interface Swim { default String move() { return "swimming"; } }

class Amphibian implements Walk, Swim {
    @Override
    public String move() {
        // pick one explicitly, or combine them
        return Walk.super.move() + " and " + Swim.super.move();
    }
}

new Amphibian().move();   // "walking and swimming"

See Also