Interface #

Imagine you’re building a payment system that supports bank transfers, credit cards, and digital wallets. Each payment method works completely differently behind the scenes, but from the perspective of the program using them, they all need to be able to do one thing: process a payment. An interface is the tool that solves this problem. An interface defines what a class must be able to do, without caring how it does it. With interfaces, you can write code that works with all payment methods at once — even methods that don’t exist yet. This article covers how interfaces work from declaration, implementation, multiple interfaces, default methods, static methods, to when to choose an interface over an abstract class.

Basic Concepts #

An interface is a contract. A class that declares implements SomeInterface promises to provide implementations for all the methods defined in that interface. If the promise isn’t kept, the program won’t compile.

There are several built-in properties of interfaces you need to understand before writing code:

ElementDefault PropertyDescription
Methods (before Java 8)public abstractNo body, must be implemented
default methods (Java 8+)publicHave a body, can be overridden but not required
static methods (Java 8+)public staticBelong to the interface, not inherited by implementors
Fields / variablespublic static finalConstants, can’t be changed

An interface can’t be instantiated directly — you can’t write new Animal() if Animal is an interface. An interface can only be used through classes that implement it.

flowchart TD
    A["«interface»\nAnimal\n─────────\nmakeSound()\neat()"] --> B["Cat\nimplements Animal"]
    A --> C["Dog\nimplements Animal"]
    A --> D["Tiger\nimplements Animal"]
    E[Client Code] -->|"Animal a = new Cat()"| A

Declaring an Interface #

Interfaces are declared with the interface keyword, not class. The methods inside have no body — only signatures (name, parameters, return type).

Basic Syntax #

// Interface declaration: only a contract, no implementation
public interface Animal {
    void makeSound();   // implicitly: public abstract void makeSound()
    void eat();   // implicitly: public abstract void eat()
    String getName();
}

Java’s interface naming convention uses adjectives or nouns that describe capabilities: Comparable, Serializable, Runnable, Closeable. This differs from classes, which are usually named after concrete nouns (Dog, BankAccount).

In Java, all fields declared inside an interface are automatically public static final — meaning constants. You can’t have instance attributes in an interface. This is a fundamental difference from abstract classes.

Implementing an Interface #

A class implements an interface with the implements keyword. All abstract methods in the interface must be provided with implementations — none can be missed.

Implementing All Methods #

public class Cat implements Animal {
    private String name;

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

    // Required: implementations of all methods from the Animal interface
    @Override
    public void makeSound() {
        System.out.println(name + " sounds: Meow!");
    }

    @Override
    public void eat() {
        System.out.println(name + " eats fish.");
    }

    @Override
    public String getName() {
        return name;
    }
}

public class Dog implements Animal {
    private String name;

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

    @Override
    public void makeSound() {
        System.out.println(name + " sounds: Woof woof!");
    }

    @Override
    public void eat() {
        System.out.println(name + " eats bones.");
    }

    @Override
    public String getName() {
        return name;
    }
}

Don’t Skip Methods #

// ANTI-PATTERN: a class implements an interface but doesn't fill in all methods
public class Bird implements Animal {
    @Override
    public void makeSound() {
        System.out.println("Tweet tweet!");
    }
    // eat() and getName() are not implemented
    // → error: Bird is not abstract and does not override abstract method eat() in Animal
}

// CORRECT: implement all methods, or make the class abstract
public class Bird implements Animal {
    private String name;

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

    @Override
    public void makeSound() {
        System.out.println(name + " sounds: Tweet tweet!");
    }

    @Override
    public void eat() {
        System.out.println(name + " eats seeds.");
    }

    @Override
    public String getName() {
        return name;
    }
}

The @Override annotation is highly recommended when implementing interface methods. The compiler will give an error if the method name or signature is mistyped — this avoids hard-to-trace bugs.


Interfaces as Data Types #

One of the most important uses of interfaces is as data types. An interface-typed variable can hold an object of any class that implements it. This is the foundation of polymorphism in Java.

Declaring Interface-Typed Variables #

// The variable type is an interface, not a concrete class
Animal cat = new Cat("Luna");
Animal dog = new Dog("Rex");
Animal bird = new Bird("Tweety");

cat.makeSound();   // Output: Luna sounds: Meow!
dog.makeSound();   // Output: Rex sounds: Woof woof!
bird.makeSound();   // Output: Tweety sounds: Tweet tweet!

Interface-Typed Method Parameters #

This pattern is very common in production code. Instead of binding code to a concrete class, you write methods that work with an interface. The result: adding a new animal type in the future doesn’t change a single line in makeAllSound().

public class Main {
    public static void main(String[] args) {
        makeAllSound(new Animal[]{
            new Cat("Luna"),
            new Dog("Rex"),
            new Bird("Tweety")
        });
    }

    // Interface-typed parameter: accepts objects of any class implementing Animal
    static void makeAllSound(Animal[] animalList) {
        for (Animal a : animalList) {
            System.out.print(a.getName() + " → ");
            a.makeSound();
        }
    }
}
sequenceDiagram
    participant Main
    participant makeAllSound
    participant cat as cat (Cat)
    participant dog as dog (Dog)
    participant bird as bird (Bird)

    Main->>makeAllSound: makeAllSound([cat, dog, bird])
    makeAllSound->>cat: getName(), makeSound()
    cat-->>makeAllSound: "Luna", "Meow!"
    makeAllSound->>dog: getName(), makeSound()
    dog-->>makeAllSound: "Rex", "Woof woof!"
    makeAllSound->>bird: getName(), makeSound()
    bird-->>makeAllSound: "Tweety", "Tweet tweet!"

Multiple Interfaces #

Java doesn’t allow one class to inherit from more than one class (extends can only target one class). But a class may implement more than one interface. This is how Java gets the flexibility of multiple inheritance without the ambiguity that comes with it.

Defining Several Interfaces #

public interface Flyable {
    void fly();
    int getMaxAltitude(); // in meters
}

public interface Swimmable {
    void swim();
    int getSwimSpeed(); // in km/h
}

public interface Runnable {
    void run();
    int getRunSpeed(); // in km/h
}

One Class, Many Interfaces #

// A duck can fly, swim, and run — implement all three interfaces
public class Duck implements Flyable, Swimmable, Runnable {
    private String name;

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

    @Override
    public void fly() {
        System.out.println(name + " flies low over the lake.");
    }

    @Override
    public int getMaxAltitude() { return 100; }

    @Override
    public void swim() {
        System.out.println(name + " swims in the lake.");
    }

    @Override
    public int getSwimSpeed() { return 3; }

    @Override
    public void run() {
        System.out.println(name + " runs in a hurry.");
    }

    @Override
    public int getRunSpeed() { return 5; }
}

Using an Object Through Various Interface Types #

Duck duck = new Duck("Donald");

// A duck can be used as Flyable, Swimmable, or Runnable
Flyable flyable = duck;
Swimmable swimmable = duck;

flyable.fly();
swimmable.swim();
flowchart LR
    A["«interface»\nFlyable"] --> C["Duck\nimplements\nFlyable, Swimmable, Runnable"]
    B["«interface»\nSwimmable"] --> C
    D["«interface»\nRunnable"] --> C
    A --> E["Eagle\nimplements Flyable"]
When one class implements two interfaces that both have a default method with the same name, Java can’t decide which one to use. You must override that method in the class and decide the implementation yourself — otherwise, the program won’t compile.

Default Methods #

Since Java 8, interfaces may have methods with implementations — called default methods. This was designed to solve one real problem: how to add a new method to an existing interface without breaking all the classes that already implement it.

Adding a Default Method to an Interface #

public interface Notification {
    void sendMessage(String message);
    String getDestination();

    // ANTI-PATTERN: adding a new abstract method to an interface already used by many classes
    // void sendMessageWithSubject(String subject, String message);
    // → all implementor classes error: must implement the new method

    // CORRECT: use a default method so existing classes aren't broken
    default void sendMessageWithSubject(String subject, String message) {
        sendMessage("[" + subject + "] " + message);
    }

    default void sendReminder(String message) {
        sendMessage("REMINDER: " + message);
    }
}

Using and Overriding Default Methods #

Old classes don’t need to change at all — they automatically inherit the default method. New classes can override it if they need different behavior.

// Old class: doesn't need to change at all
public class EmailNotification implements Notification {
    private String destinationEmail;

    public EmailNotification(String destinationEmail) { this.destinationEmail = destinationEmail; }

    @Override
    public void sendMessage(String message) {
        System.out.println("Email to " + destinationEmail + ": " + message);
    }

    @Override
    public String getDestination() { return destinationEmail; }
    // sendMessageWithSubject() and sendReminder() are available via default methods
}

// New class: overrides the default method because SMS has a different format
public class SMSNotification implements Notification {
    private String destinationNumber;

    public SMSNotification(String destinationNumber) { this.destinationNumber = destinationNumber; }

    @Override
    public void sendMessage(String message) {
        System.out.println("SMS to " + destinationNumber + ": " + message);
    }

    @Override
    public String getDestination() { return destinationNumber; }

    @Override
    public void sendMessageWithSubject(String subject, String message) {
        sendMessage(subject.toUpperCase() + " - " + message);
    }
}
Notification email = new EmailNotification("[email protected]");
Notification sms   = new SMSNotification("+628****7890");

email.sendMessageWithSubject("Invoice", "This month's invoice is due.");
// Output: Email to [email protected]: [Invoice] This month's invoice is due.

sms.sendMessageWithSubject("Invoice", "This month's invoice is due.");
// Output: SMS to +628****7890: INVOICE - This month's invoice is due.

Static Methods #

Besides default methods, Java 8 also introduced static methods in interfaces. Unlike default methods, static methods aren’t inherited by implementing classes — they’re called directly through the interface name.

Defining a Static Method #

public interface Validator {
    boolean validate(String input);

    // Static method: utility related to this interface
    static boolean notEmpty(String value) {
        return value != null && !value.trim().isEmpty();
    }

    static boolean validLength(String value, int min, int max) {
        if (!notEmpty(value)) return false;
        int length = value.trim().length();
        return length >= min && length <= max;
    }
}

Calling Static Methods #

public class EmailValidator implements Validator {
    @Override
    public boolean validate(String email) {
        if (!Validator.notEmpty(email)) return false;
        return email.contains("@") && email.contains(".");
    }
}

public class Main {
    public static void main(String[] args) {
        Validator emailValidator = new EmailValidator();
        System.out.println(emailValidator.validate("[email protected]")); // true
        System.out.println(emailValidator.validate("not-an-email"));       // false

        // Static methods are called directly through the interface name
        System.out.println(Validator.notEmpty(""));    // false
        System.out.println(Validator.notEmpty("abc")); // true

        // ANTI-PATTERN: trying to call a static method through an instance
        // emailValidator.notEmpty("abc"); // error: can't do that
    }
}

Interfaces in the Java Standard Library #

Interfaces aren’t just an abstract concept — the Java standard library itself is built on them. Understanding the existing interfaces helps you write more idiomatic, interoperable code.

InterfacePackageUse
Comparable<T>java.langDefines an object’s natural ordering (compareTo)
Comparator<T>java.utilDefines custom ordering from outside the class
Iterable<T>java.langMakes an object usable in for-each loops
Runnablejava.langDefines a task that can run in a thread
Callable<V>java.util.concurrentLike Runnable but can return a value and throw exceptions
Closeablejava.ioDefines a closeable resource (try-with-resources)

Example: Implementing Comparable #

A Product class implementing Comparable so it can be sorted automatically by Collections.sort():

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Product implements Comparable<Product> {
    private String name;
    private double price;

    public Product(String name, double price) {
        this.name = name;
        this.price = price;
    }

    // Comparable implementation: sort by price (ascending)
    @Override
    public int compareTo(Product other) {
        return Double.compare(this.price, other.price);
    }

    @Override
    public String toString() {
        return name + " ($ " + price + ")";
    }
}

public class Main {
    public static void main(String[] args) {
        List<Product> productList = new ArrayList<>();
        productList.add(new Product("Laptop", 12000000));
        productList.add(new Product("Mouse", 150000));
        productList.add(new Product("Monitor", 3500000));
        productList.add(new Product("Keyboard", 450000));

        // Collections.sort() works because Product implements Comparable
        Collections.sort(productList);

        for (Product p : productList) {
            System.out.println(p);
        }
        // Output (sorted from cheapest):
        // Mouse ($ 150000.0)
        // Keyboard ($ 450000.0)
        // Monitor ($ 3500000.0)
        // Laptop ($ 12000000.0)
    }
}

Interface vs Abstract Class #

This is a question that comes up often. Both can define contracts, but they have different characteristics. Choose based on specific needs, not preference.

AspectInterfaceAbstract Class
InstantiationNot possibleNot possible
Multiple inheritance✓ One class can implement many✗ Only one extends
Instance attributes✗ Not possible (only constants)✓ Can have attributes
Constructor✗ None✓ Exists
Concrete methods✓ Via default / static✓ Regular methods
Method access modifiersOnly publicCan be public, protected, private
Relationship“Can do” (capability)“Is a type of” (hierarchy)

Decision Tree #

flowchart TD
    A{Is there shared\nstate/attributes?} -- Yes --> B[Abstract Class]
    A -- No --> C{Need multiple\ninheritance?}
    C -- Yes --> D[Interface]
    C -- No --> E{Is the relationship\n'is-a' or 'can-do'?}
    E -- "'is-a'\n(Dog is an Animal)" --> F[Abstract Class]
    E -- "'can-do'\n(Bird can Fly)" --> G[Interface]

Combining Both #

Abstract classes and interfaces can be used together. The abstract class defines the hierarchy and state, while interfaces add capabilities across the hierarchy.

// Abstract class: hierarchy and shared state
abstract class Employee {
    protected String name;
    protected double baseSalary;

    public Employee(String name, double baseSalary) {
        this.name = name;
        this.baseSalary = baseSalary;
    }

    public String getName() { return name; }
    abstract double calculateSalary();
}

// Interfaces: extra capabilities anyone can have
interface CanOvertime {
    double calculateOvertime(int overtimeHours);
}

interface CanBonus {
    double calculateBonus(double percentage);
}

// Senior employee: has the hierarchy from PermanentEmployee + overtime & bonus capabilities
class SeniorPermanentEmployee extends Employee implements CanOvertime, CanBonus {
    public SeniorPermanentEmployee(String name, double baseSalary) {
        super(name, baseSalary);
    }

    @Override
    double calculateSalary() { return baseSalary; }

    @Override
    public double calculateOvertime(int overtimeHours) {
        return (baseSalary / 173) * 1.5 * overtimeHours;
    }

    @Override
    public double calculateBonus(double percentage) {
        return baseSalary * (percentage / 100);
    }
}

When to Use Interfaces #

Use an INTERFACE when:
  ✓ You're defining capabilities that unrelated classes can have
    (Flyable can belong to both Bird and Plane — two unrelated things)
  ✓ You need multiple inheritance
  ✓ You want an API that others can implement (library, plugin)
  ✓ There's no shared state to carry

Use an ABSTRACT CLASS when:
  ✓ There's a clear "is-a" relationship in a hierarchy
  ✓ Subclasses need attributes or constructors from the parent class
  ✓ There's a default implementation that needs access to state (instance attributes)
  ✓ You want methods with access modifiers other than public

Use BOTH when:
  ✓ The abstract class defines the hierarchy, interfaces add cross-hierarchy capabilities
  ✓ Example: abstract class Employee + interfaces CanOvertime, CanBonus

Summary #

  • An interface is a contract — it defines what must be doable, not how. A class that implements it must provide all its abstract methods.
  • All interface methods are public abstract by default — no need to write it explicitly. Fields in an interface are automatically public static final (constants).
  • Interfaces can be used as data types — this is the foundation of polymorphism. Code written against an interface works with every class implementing it, including classes that don’t exist yet.
  • One class can implement many interfaces — this is how Java achieves multiple inheritance without ambiguity. Use it to add capabilities across class hierarchies.
  • default methods (Java 8+) enable API evolution — add new methods to an interface without breaking all existing implementors. Implementors can override if needed, or use the default implementation.
  • static methods in interfaces are utilities — not inherited by implementors, called via the interface name (InterfaceName.methodName()).
  • Interface vs abstract class — choose an interface for capabilities (“can fly”), choose an abstract class for hierarchy (“is an employee”). Both can be used together.
  • The Java standard library is built on interfacesComparable, Runnable, Iterable, Closeable are real examples. Implement the relevant interfaces so your code works smoothly with the Java ecosystem.

← Previous: Classes   Next: List →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact