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:
| Element | Default Property | Description |
|---|---|---|
| Methods (before Java 8) | public abstract | No body, must be implemented |
default methods (Java 8+) | public | Have a body, can be overridden but not required |
static methods (Java 8+) | public static | Belong to the interface, not inherited by implementors |
| Fields / variables | public static final | Constants, 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()"| ADeclaring 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.
| Interface | Package | Use |
|---|---|---|
Comparable<T> | java.lang | Defines an object’s natural ordering (compareTo) |
Comparator<T> | java.util | Defines custom ordering from outside the class |
Iterable<T> | java.lang | Makes an object usable in for-each loops |
Runnable | java.lang | Defines a task that can run in a thread |
Callable<V> | java.util.concurrent | Like Runnable but can return a value and throw exceptions |
Closeable | java.io | Defines 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.
| Aspect | Interface | Abstract Class |
|---|---|---|
| Instantiation | Not possible | Not 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 modifiers | Only public | Can 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
implementsit must provide all its abstract methods.- All interface methods are
public abstractby default — no need to write it explicitly. Fields in an interface are automaticallypublic 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 methodsin 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 interfaces —
Comparable,Runnable,Iterable,Closeableare real examples. Implement the relevant interfaces so your code works smoothly with the Java ecosystem.