Java Interfaces

Java allows only single inheritance. A class can extend one parent. But what if a class needs capabilities from multiple sources? Interfaces solve this. They define what a class can do without specifying how.

An interface is a contract. Any class that implements the interface promises to provide certain methods. This enables polymorphism across unrelated classes.

Defining an Interface

An interface declares method signatures without implementations:

public interface Drawable {
    void draw();
}

The Drawable interface declares one method: draw(). Any class implementing Drawable must provide a draw() method.

public interface Playable {
    void play();
    void pause();
    void stop();
}

Playable requires three methods. A class implementing it must provide all three.

Implementing an Interface

A class uses the implements keyword to adopt an interface:

public class Circle implements Drawable {
    private int radius;
    
    public Circle(int radius) {
        this.radius = radius;
    }
    
    @Override
    public void draw() {
        System.out.println("Drawing circle with radius " + radius);
    }
}

public class Rectangle implements Drawable {
    private int width;
    private int height;
    
    public Rectangle(int width, int height) {
        this.width = width;
        this.height = height;
    }
    
    @Override
    public void draw() {
        System.out.println("Drawing rectangle " + width + "x" + height);
    }
}

Both Circle and Rectangle implement Drawable. Each provides its own draw() method. The @Override annotation confirms you’re implementing an interface method.

Using Interface Types

Like inheritance, interfaces enable polymorphism. A variable of the interface type can hold any implementing object:

Drawable shape1 = new Circle(5);
Drawable shape2 = new Rectangle(10, 20);

shape1.draw();  // Drawing circle with radius 5
shape2.draw();  // Drawing rectangle 10x20

This works with arrays and collections:

ArrayList<Drawable> shapes = new ArrayList<>();
shapes.add(new Circle(5));
shapes.add(new Rectangle(10, 20));
shapes.add(new Circle(3));

for (Drawable shape : shapes) {
    shape.draw();
}

One loop handles all Drawable objects regardless of their actual class.

Multiple Interfaces

A class can implement multiple interfaces. This is how Java achieves a form of multiple inheritance:

public interface Drawable {
    void draw();
}

public interface Resizable {
    void resize(double factor);
}

public interface Movable {
    void move(int x, int y);
}

public class Shape implements Drawable, Resizable, Movable {
    private int x, y;
    private int size;
    
    public Shape(int x, int y, int size) {
        this.x = x;
        this.y = y;
        this.size = size;
    }
    
    @Override
    public void draw() {
        System.out.println("Drawing shape at (" + x + "," + y + ") size " + size);
    }
    
    @Override
    public void resize(double factor) {
        size = (int)(size * factor);
        System.out.println("Resized to " + size);
    }
    
    @Override
    public void move(int x, int y) {
        this.x = x;
        this.y = y;
        System.out.println("Moved to (" + x + "," + y + ")");
    }
}

Shape fulfills three contracts. It can be used wherever any of those interfaces is expected:

Shape s = new Shape(0, 0, 100);

Drawable d = s;    // Valid
Resizable r = s;   // Valid
Movable m = s;     // Valid

Interfaces vs Abstract Classes

Both define contracts, but they serve different purposes:

Interfaces:

  • Define what a class can do
  • A class can implement multiple interfaces
  • No constructor
  • All methods were traditionally abstract (this has changed)

Abstract classes:

  • Define what a class is
  • A class can extend only one abstract class
  • Can have constructors
  • Can have both abstract and concrete methods

Use interfaces when unrelated classes need common behavior. Use abstract classes when related classes share common implementation.

Default Methods

Since Java 8, interfaces can include default implementations:

public interface Greeting {
    void sayHello();
    
    default void sayGoodbye() {
        System.out.println("Goodbye!");
    }
}

Classes implementing Greeting must provide sayHello() but inherit sayGoodbye() automatically:

public class EnglishGreeting implements Greeting {
    @Override
    public void sayHello() {
        System.out.println("Hello!");
    }
    // sayGoodbye() is inherited from the interface
}

EnglishGreeting g = new EnglishGreeting();
g.sayHello();    // Hello!
g.sayGoodbye();  // Goodbye!

Classes can override default methods if needed:

public class FormalGreeting implements Greeting {
    @Override
    public void sayHello() {
        System.out.println("Good day to you.");
    }
    
    @Override
    public void sayGoodbye() {
        System.out.println("Farewell.");
    }
}

Default methods let interfaces evolve without breaking existing implementations.

Static Methods in Interfaces

Interfaces can also have static methods:

public interface MathOperations {
    int calculate(int a, int b);
    
    static int add(int a, int b) {
        return a + b;
    }
    
    static int multiply(int a, int b) {
        return a * b;
    }
}

// Call static methods on the interface
int sum = MathOperations.add(5, 3);       // 8
int product = MathOperations.multiply(5, 3);  // 15

Static methods provide utility functions related to the interface.

Constants in Interfaces

Any field declared in an interface is implicitly public, static, and final:

public interface GameConstants {
    int MAX_PLAYERS = 4;
    int DEFAULT_LIVES = 3;
    String GAME_TITLE = "Adventure Quest";
}

// Access like any static constant
System.out.println(GameConstants.MAX_PLAYERS);  // 4

This pattern was common before enums existed. Today, enums or regular classes are often better for constants.

Common Java Interfaces

Java’s standard library includes many interfaces you’ll encounter:

Comparable: Defines natural ordering for sorting.

public class Student implements Comparable<Student> {
    private String name;
    private int grade;
    
    public Student(String name, int grade) {
        this.name = name;
        this.grade = grade;
    }
    
    @Override
    public int compareTo(Student other) {
        return this.grade - other.grade;  // Sort by grade
    }
    
    @Override
    public String toString() {
        return name + ": " + grade;
    }
}
ArrayList<Student> students = new ArrayList<>();
students.add(new Student("Alice", 85));
students.add(new Student("Bob", 92));
students.add(new Student("Charlie", 78));

Collections.sort(students);

for (Student s : students) {
    System.out.println(s);
}
// Charlie: 78
// Alice: 85
// Bob: 92

List, Set, Map: Collection interfaces you’ve used through ArrayList and others.

Runnable: Defines a task that can run in a thread.

Serializable: Marks a class as saveable to files or streams.

Practical Example: Plugin System

Interfaces are perfect for plugin architectures where you want extensibility:

public interface PaymentProcessor {
    boolean processPayment(double amount);
    String getProcessorName();
    default void printReceipt(double amount) {
        System.out.println("Receipt: $" + amount + " via " + getProcessorName());
    }
}

public class CreditCardProcessor implements PaymentProcessor {
    @Override
    public boolean processPayment(double amount) {
        System.out.println("Processing credit card payment: $" + amount);
        return true;
    }
    
    @Override
    public String getProcessorName() {
        return "Credit Card";
    }
}

public class PayPalProcessor implements PaymentProcessor {
    @Override
    public boolean processPayment(double amount) {
        System.out.println("Processing PayPal payment: $" + amount);
        return true;
    }
    
    @Override
    public String getProcessorName() {
        return "PayPal";
    }
}

public class CryptoProcessor implements PaymentProcessor {
    @Override
    public boolean processPayment(double amount) {
        System.out.println("Processing crypto payment: $" + amount);
        return true;
    }
    
    @Override
    public String getProcessorName() {
        return "Cryptocurrency";
    }
}

A checkout system works with any processor:

public class Checkout {
    private PaymentProcessor processor;
    
    public Checkout(PaymentProcessor processor) {
        this.processor = processor;
    }
    
    public void completePurchase(double amount) {
        if (processor.processPayment(amount)) {
            processor.printReceipt(amount);
            System.out.println("Purchase complete!");
        } else {
            System.out.println("Payment failed.");
        }
    }
}

// Use with any processor
Checkout checkout1 = new Checkout(new CreditCardProcessor());
checkout1.completePurchase(99.99);

Checkout checkout2 = new Checkout(new PayPalProcessor());
checkout2.completePurchase(49.50);

Adding a new payment method requires only a new class implementing PaymentProcessor. The Checkout class doesn’t change.

Functional Interfaces

An interface with exactly one abstract method is a functional interface. These can be used with lambda expressions:

@FunctionalInterface
public interface Calculator {
    int calculate(int a, int b);
}

// Traditional implementation
Calculator adder = new Calculator() {
    @Override
    public int calculate(int a, int b) {
        return a + b;
    }
};

// Lambda expression (shorter)
Calculator multiplier = (a, b) -> a * b;

System.out.println(adder.calculate(5, 3));      // 8
System.out.println(multiplier.calculate(5, 3)); // 15

The @FunctionalInterface annotation is optional but documents your intent and catches errors if you accidentally add a second abstract method.

Common Mistakes

Forgetting to implement all methods. If you implement an interface, you must provide all its abstract methods or declare your class abstract.

Confusing extends and implements. Classes extend other classes. Classes implement interfaces. Interfaces extend other interfaces.

class Child extends Parent { }           // Class extends class
class MyClass implements MyInterface { } // Class implements interface
interface SubInterface extends SuperInterface { } // Interface extends interface

Overusing interfaces. Not every class needs an interface. Use interfaces when you expect multiple implementations or want to define a contract for external code.

Too many methods in one interface. Keep interfaces focused. Many small interfaces are better than one large one. This is called the Interface Segregation Principle.

What’s Next

Interfaces define pure contracts. Abstract classes sit between interfaces and concrete classes, providing partial implementations. The next tutorial covers abstract classes and when to use them instead of interfaces.


Related: Java Encapsulation | Abstract Classes in Java

Sources

  • Oracle. “Interfaces.” The Java Tutorials. docs.oracle.com
  • Oracle. “Default Methods.” The Java Tutorials. docs.oracle.com
Scroll to Top