Java Interface

June 30, 20264 min readUpdated 8/20/2026

An interface is a contract: a list of methods a class promises to provide, with no state and (mostly) no implementation. It is the main tool Java gives you for writing code that does not depend on a specific class — and that is what makes code testable and changeable.

Declaring and implementing one

interface Shape {
    double area();                       // no body — implementers must supply one
    double perimeter();
}

class Circle implements Shape {
    private final double radius;
    Circle(double radius) { this.radius = radius; }

    @Override public double area() { return Math.PI * radius * radius; }
    @Override public double perimeter() { return 2 * Math.PI * radius; }
}

class Rectangle implements Shape {
    private final double w, h;
    Rectangle(double w, double h) { this.w = w; this.h = h; }

    @Override public double area() { return w * h; }
    @Override public double perimeter() { return 2 * (w + h); }
}

Interface methods are public and abstract automatically, so those keywords are usually omitted from the declaration — but the implementing methods must be explicitly public, because you cannot reduce visibility when overriding.

If a class says implements Shape and misses a method, it does not compile. That is the contract being enforced.

Why bother — the actual payoff

interface Shape { double area(); }

class Report {
    // Takes ANY Shape. Never needs changing when a new shape is written.
    double totalArea(List<Shape> shapes) {
        double total = 0;
        for (Shape shape : shapes) {
            total += shape.area();
        }
        return total;
    }
}

Compare that with a method that took a List<Circle>. Adding triangles would mean editing it. Here, Report depends on the contract, so new implementations arrive without it noticing.

The same property is what makes testing possible. Depend on an interface and a test can supply a fake implementation instead of a real database or payment gateway:

interface PaymentGateway {
    boolean charge(double amount);
}

class OrderService {
    private final PaymentGateway gateway;

    OrderService(PaymentGateway gateway) {      // handed in, not constructed here
        this.gateway = gateway;
    }

    String placeOrder(double amount) {
        return gateway.charge(amount) ? "confirmed" : "declined";
    }
}

class OrderServiceTest {
    void chargesAreDeclinedGracefully() {
        OrderService service = new OrderService(amount -> false);  // fake, no network
        System.out.println(service.placeOrder(20).equals("declined"));   // true
    }
}

That is dependency injection in its simplest form, and interfaces are what make it work.

Many interfaces, one superclass

interface Swimmer { void swim(); }
interface Flyer { void fly(); }

class Duck implements Swimmer, Flyer {
    @Override public void swim() { System.out.println("paddling"); }
    @Override public void fly() { System.out.println("flapping"); }
}

A class can implement any number of interfaces but extend only one class. The reason is state: two superclasses could each bring a field with the same name and Java would not know which one an object has. Interfaces have no fields, so the problem never arises.

default and static methods

Java 8 allowed interfaces to carry implementations. A default method has a body that implementers inherit unless they override it:

interface Shape {
    double area();

    default String describe() {                    // implementers get this free
        return getClass().getSimpleName() + " with area " + area();
    }

    static Shape unitSquare() {                    // a factory, called on the interface
        return () -> 1.0;
    }
}

This exists for a specific reason worth knowing: backwards compatibility. Adding a method to an interface breaks every existing implementation. Adding a default method does not. That is how List gained forEach and removeIf in Java 8 without breaking every List ever written.

Use default for that purpose — evolving an interface, or a genuine convenience derived from the other methods. Do not use it to smuggle in shared implementation; that is what an abstract class is for.

Functional interfaces

An interface with exactly one abstract method can be implemented by a lambda:

@FunctionalInterface
interface Validator {
    boolean test(String input);
}

class Demo {
    void run() {
        Validator notBlank = s -> s != null && !s.isBlank();
        Validator shortEnough = s -> s.length() <= 10;

        System.out.println(notBlank.test("hi"));        // true
        System.out.println(shortEnough.test("hi"));     // true
    }
}

The @FunctionalInterface annotation is optional but worth adding — it makes the compiler reject a second abstract method, so nobody accidentally breaks every lambda using your interface. This is the foundation of lambdas and streams, which posts 19 and 20 cover.

Interface or abstract class?

InterfaceAbstract class
State (fields)No — only constantsYes
ConstructorsNoYes
How many per classManyOne
Access modifiersPublic (plus private helpers)Any, including protected
Says"can do this""is a kind of this"

The practical rule: reach for an interface first. It keeps the implementer's one inheritance slot free and describes a capability rather than a family. Use an abstract class only when subclasses genuinely share fields or constructor logic that would otherwise be copied.

They combine well, and often should: declare the contract as an interface, and provide an abstract class implementing the tedious parts for anyone who wants it.

Constants, and a warning

Fields in an interface are implicitly public static final. This occasionally tempts people into an interface that holds nothing but constants, implemented purely to import them — sometimes called the constant interface antipattern. It leaks implementation detail into your public type. Use a final class with static fields, or an enum, instead.

Next

static and final have appeared repeatedly without a proper account. static and final covers both.