Interfaces in Java

1. What is an Interface?

An interface in Java is a reference type that defines a contract — a set of method signatures that any implementing class must fulfil. Think of it as an agreement: "Any class that says it implements this interface promises to provide these behaviours."

Key rules for interfaces (before Java 8):

  • All methods are implicitly public abstract
  • All fields are implicitly public static final (constants)
  • Interfaces cannot be instantiated directly
  • A class can implement multiple interfaces
  • An interface can extend multiple interfaces

Java 8+ added default and static methods to interfaces. Java 9+ added private methods. The core contract concept remains unchanged.

2. Defining and Implementing an Interface

Use the interface keyword to define and implements to use it in a class. The implementing class must provide a body for every abstract method in the interface, or itself be declared abstract.

// Define the interface
public interface Drawable {
    // public abstract — implicit; no need to write them
    void draw();
    void resize(double factor);

    // Default method (Java 8+) — optional to override
    default void printInfo() {
        System.out.println("This is a Drawable object.");
    }
}

// Implement the interface in a class
public class Circle implements Drawable {
    double radius;
    String color;

    public Circle(double radius, String color) {
        this.radius = radius;
        this.color  = color;
    }

    @Override
    public void draw() {
        System.out.println("Drawing a " + color + " circle with radius " + radius);
    }

    @Override
    public void resize(double factor) {
        radius *= factor;
        System.out.println("Circle resized. New radius: " + radius);
    }
}

public class Square implements Drawable {
    double side;

    public Square(double side) {
        this.side = side;
    }

    @Override
    public void draw() {
        System.out.println("Drawing a square with side " + side);
    }

    @Override
    public void resize(double factor) {
        side *= factor;
        System.out.println("Square resized. New side: " + side);
    }

    @Override
    public void printInfo() {   // optionally override default method
        System.out.println("Square: side = " + side);
    }
}

public class Main {
    public static void main(String[] args) {
        Drawable c = new Circle(5.0, "red");  // interface reference
        Drawable s = new Square(4.0);

        c.draw();        // Drawing a red circle with radius 5.0
        c.resize(2.0);   // Circle resized. New radius: 10.0
        c.printInfo();   // This is a Drawable object.

        s.draw();        // Drawing a square with side 4.0
        s.printInfo();   // Square: side = 4.0  (overridden)
    }
}
Drawing a red circle with radius 5.0
Circle resized. New radius: 10.0
This is a Drawable object.
Drawing a square with side 4.0
Square: side = 4.0

3. Interface vs Abstract Class

Feature Interface Abstract Class
Keyword interface / implements abstract class / extends
Instantiation Cannot be instantiated Cannot be instantiated
Constructors No Yes
Instance fields No (only public static final) Yes (any access modifier)
Method types abstract, default, static (Java 8+) abstract + concrete
Multiple inheritance A class can implement many interfaces A class can extend only one
Access modifiers on methods Always public (implicit) Any access modifier
Speed Slightly slower (extra lookup) Slightly faster
Best used when Defining capabilities/contracts across unrelated classes Sharing common code & state among related classes

Rule of thumb: Prefer interfaces when you want to define a capability (e.g., Comparable, Serializable). Use abstract classes when you want to share code and state among closely related classes.

4. Multiple Interface Implementation

A Java class can implement multiple interfaces simultaneously, separated by commas. This is Java's approach to achieving multiple inheritance of type without the complexity of multiple class inheritance.

public interface Flyable {
    void fly();
    default String getMode() { return "flying"; }
}

public interface Swimmable {
    void swim();
    default String getMode() { return "swimming"; }
}

public interface Walkable {
    void walk();
}

// Duck implements all three interfaces
public class Duck implements Flyable, Swimmable, Walkable {

    private String name;

    public Duck(String name) { this.name = name; }

    @Override
    public void fly() {
        System.out.println(name + " is flying!");
    }

    @Override
    public void swim() {
        System.out.println(name + " is swimming!");
    }

    @Override
    public void walk() {
        System.out.println(name + " is walking!");
    }

    // MUST override getMode() — both Flyable and Swimmable define it
    // (this resolves the diamond problem for default methods)
    @Override
    public String getMode() {
        return Flyable.super.getMode() + " and " + Swimmable.super.getMode();
    }
}

public class Main {
    public static void main(String[] args) {
        Duck duck = new Duck("Donald");
        duck.fly();
        duck.swim();
        duck.walk();
        System.out.println("Duck modes: " + duck.getMode());

        // Polymorphic references
        Flyable   f = duck;  f.fly();
        Swimmable s = duck;  s.swim();
    }
}
Donald is flying!
Donald is swimming!
Donald is walking!
Duck modes: flying and swimming
Donald is flying!
Donald is swimming!

Diamond Problem: When two interfaces define the same default method, the implementing class must override it to resolve the conflict. Use InterfaceName.super.methodName() to call a specific interface's default implementation.

5. Default Methods (Java 8+)

Java 8 introduced default methods — interface methods with a body, declared with the default keyword. They allow adding new methods to existing interfaces without breaking all implementing classes.

public interface Logger {
    // Abstract method — must be implemented
    void log(String message);

    // Default method — implementation provided, can be overridden
    default void logInfo(String message) {
        log("[INFO] " + message);
    }

    default void logError(String message) {
        log("[ERROR] " + message);
    }

    default void logWarning(String message) {
        log("[WARN] " + message);
    }
}

// Simple console logger — only needs to implement log()
public class ConsoleLogger implements Logger {
    @Override
    public void log(String message) {
        System.out.println(message);
    }
    // inherits logInfo, logError, logWarning for free
}

// File logger — overrides one default method
public class FileLogger implements Logger {
    private String filename;

    public FileLogger(String filename) { this.filename = filename; }

    @Override
    public void log(String message) {
        // In real code: write to file. Here we simulate:
        System.out.println("[FILE:" + filename + "] " + message);
    }

    @Override
    public void logError(String message) {
        // Custom error handling — also alert
        log("[ERROR] " + message);
        System.out.println("*** ALERT: Error written to " + filename + " ***");
    }
}

public class Main {
    public static void main(String[] args) {
        Logger console = new ConsoleLogger();
        console.logInfo("Application started");
        console.logWarning("Low memory");
        console.logError("Null pointer detected");

        System.out.println("---");

        Logger file = new FileLogger("app.log");
        file.logInfo("Application started");
        file.logError("Disk full");   // uses overridden version
    }
}
[INFO] Application started
[WARN] Low memory
[ERROR] Null pointer detected
---
[FILE:app.log] [INFO] Application started
[FILE:app.log] [ERROR] Disk full
*** ALERT: Error written to app.log ***

Why default methods were added: Java 8 needed to add new methods to java.util.Collection (like forEach, stream) without breaking the thousands of existing classes that implement it.

6. Static Methods in Interfaces (Java 8+)

Interfaces can also define static methods — utility or factory methods related to the interface. They belong to the interface itself and are not inherited by implementing classes. You call them via the interface name.

public interface MathUtils {
    // Abstract
    double calculate(double a, double b);

    // Static utility methods — called as MathUtils.methodName()
    static double square(double x) {
        return x * x;
    }

    static double cube(double x) {
        return x * x * x;
    }

    static boolean isPrime(int n) {
        if (n < 2) return false;
        for (int i = 2; i <= Math.sqrt(n); i++) {
            if (n % i == 0) return false;
        }
        return true;
    }

    static double clamp(double value, double min, double max) {
        return Math.max(min, Math.min(max, value));
    }
}

public class Adder implements MathUtils {
    @Override
    public double calculate(double a, double b) { return a + b; }
}

public class Main {
    public static void main(String[] args) {
        // Static methods called on the interface, not the class
        System.out.println(MathUtils.square(5));      // 25.0
        System.out.println(MathUtils.cube(3));        // 27.0
        System.out.println(MathUtils.isPrime(17));    // true
        System.out.println(MathUtils.isPrime(10));    // false
        System.out.println(MathUtils.clamp(150, 0, 100)); // 100.0

        // Instance method still works
        Adder adder = new Adder();
        System.out.println(adder.calculate(3, 4));    // 7.0

        // adder.square(5);  // COMPILE ERROR — static methods not inherited
    }
}
25.0
27.0
true
false
100.0
7.0

7. Functional Interface

A functional interface is an interface with exactly one abstract method. It can have any number of default or static methods. Annotate it with @FunctionalInterface to let the compiler enforce the one-abstract-method rule. Functional interfaces are the foundation of lambda expressions in Java 8+.

@FunctionalInterface
public interface Transformer {
    String transform(String input);  // the single abstract method

    // default and static methods allowed
    default Transformer andThen(Transformer after) {
        return input -> after.transform(this.transform(input));
    }

    static Transformer identity() {
        return input -> input;
    }
}

public class Main {
    public static void main(String[] args) {
        // Lambda — short form for implementing the single abstract method
        Transformer upper = s -> s.toUpperCase();
        Transformer trim  = s -> s.trim();
        Transformer exclaim = s -> s + "!";

        System.out.println(upper.transform("  hello  "));   // "  HELLO  "
        System.out.println(trim.transform("  hello  "));    // "hello"

        // Chaining with andThen (default method)
        Transformer combined = trim.andThen(upper).andThen(exclaim);
        System.out.println(combined.transform("  java  ")); // "JAVA!"

        // Anonymous class — the old way before lambdas
        Transformer reverse = new Transformer() {
            @Override
            public String transform(String input) {
                return new StringBuilder(input).reverse().toString();
            }
        };
        System.out.println(reverse.transform("Hello"));     // "olleH"

        // Built-in functional interfaces from java.util.function
        java.util.function.Predicate<Integer> isEven = n -> n % 2 == 0;
        java.util.function.Function<Integer, String> intToStr = n -> "Value: " + n;

        System.out.println(isEven.test(4));         // true
        System.out.println(intToStr.apply(42));     // Value: 42
    }
}
HELLO
hello
JAVA!
olleH
true
Value: 42

Java's java.util.function package ships with many ready-made functional interfaces: Predicate<T>, Function<T,R>, Consumer<T>, Supplier<T>, BiFunction<T,U,R>, and more.

8. Nested Interface

An interface declared inside a class or another interface is called a nested interface. It is implicitly public static when inside another interface, and can have any access modifier when inside a class.

// Nested interface inside a class
public class DataProcessor {

    // Nested interface — accessed as DataProcessor.Validator
    public interface Validator<T> {
        boolean validate(T value);
        default boolean isInvalid(T value) { return !validate(value); }
    }

    private Validator<String> validator;

    public DataProcessor(Validator<String> validator) {
        this.validator = validator;
    }

    public void process(String data) {
        if (validator.validate(data)) {
            System.out.println("Processing: " + data);
        } else {
            System.out.println("Rejected: '" + data + "' failed validation.");
        }
    }
}

// Interface nesting inside another interface
public interface OuterInterface {
    void outerMethod();

    interface InnerInterface {       // implicitly public static
        void innerMethod();
    }
}

// Implementing nested interface
class ConcreteOuter implements OuterInterface, OuterInterface.InnerInterface {
    @Override public void outerMethod() { System.out.println("Outer method"); }
    @Override public void innerMethod() { System.out.println("Inner method"); }
}

public class Main {
    public static void main(String[] args) {
        // Use lambda to implement nested Validator
        DataProcessor.Validator<String> nonEmpty = s -> s != null && !s.trim().isEmpty();
        DataProcessor dp = new DataProcessor(nonEmpty);

        dp.process("Hello World");   // Processing: Hello World
        dp.process("");              // Rejected: '' failed validation.
        dp.process("  ");            // Rejected: '  ' failed validation.

        ConcreteOuter co = new ConcreteOuter();
        co.outerMethod();            // Outer method
        co.innerMethod();            // Inner method
    }
}
Processing: Hello World
Rejected: '' failed validation.
Rejected: ' ' failed validation.
Outer method
Inner method

Marker interfaces add metadata or capabilities to a class. Continue with the complete beginner-friendly guide covering Serializable, deserialization, serialVersionUID, Externalizable, and Cloneable.

Open Marker Interface Tutorial →

10. Interface with Constants

All fields declared in an interface are implicitly public static final, making them constants. While this is a valid pattern, prefer using enum or dedicated constant classes in modern Java.

public interface AppConfig {
    // All fields are implicitly public static final
    String APP_NAME    = "MyJavaApp";
    String VERSION     = "2.1.0";
    int    MAX_RETRIES = 3;
    double TIMEOUT_SEC = 30.0;

    // Nested constants grouped by category
    interface HttpStatus {
        int OK                    = 200;
        int CREATED               = 201;
        int BAD_REQUEST           = 400;
        int UNAUTHORIZED          = 401;
        int NOT_FOUND             = 404;
        int INTERNAL_SERVER_ERROR = 500;
    }

    interface DatabaseConfig {
        String HOST     = "localhost";
        int    PORT     = 5432;
        String DB_NAME  = "mydb";
    }

    // Methods can also be declared
    void initialize();
}

public class Application implements AppConfig {
    @Override
    public void initialize() {
        System.out.println("Starting " + APP_NAME + " v" + VERSION);
        System.out.println("Max retries: " + MAX_RETRIES);
        System.out.println("Timeout: " + TIMEOUT_SEC + "s");
    }

    public void connect() {
        System.out.println("Connecting to " + DatabaseConfig.HOST
            + ":" + DatabaseConfig.PORT + "/" + DatabaseConfig.DB_NAME);
    }
}

public class Main {
    public static void main(String[] args) {
        Application app = new Application();
        app.initialize();
        app.connect();

        // Access constants directly via interface name
        System.out.println("HTTP OK: " + AppConfig.HttpStatus.OK);
        System.out.println("HTTP Not Found: " + AppConfig.HttpStatus.NOT_FOUND);

        // Implementing class also gets direct access
        System.out.println("App timeout: " + app.TIMEOUT_SEC + "s");
    }
}
Starting MyJavaApp v2.1.0
Max retries: 3
Timeout: 30.0s
Connecting to localhost:5432/mydb
HTTP OK: 200
HTTP Not Found: 404
App timeout: 30.0s

Constant Interface Anti-Pattern: Using interfaces solely to hold constants (the "Constant Interface" pattern) is considered bad practice by Josh Bloch in Effective Java. Prefer enum types or static utility classes with private constructors for constants.

Continue learning