Free lesson · Java Basic To Advance
Introduction to Interfaces
The Contract Without the Implementation
Module 8 covered inheritance — one class extending another, gaining state and behavior. This Module covers a different mechanism for polymorphism: interfaces. An interface declares a contract — what methods a class must provide — without specifying how those methods work.
Where inheritance says "Dog IS-A Animal with these inherited capabilities," interfaces say "Duck CAN-DO swim, fly, quack — and any class that wants to claim it can do these is welcome to implement them."
Brandon Carter continuing through Module 9. Interfaces are Java's most-used polymorphism mechanism in modern code. The Collections framework is built on them. Streams are built on them. Most modern frameworks (Spring, JUnit, JPA) depend on interfaces heavily. Mastering interfaces is non-negotiable for senior Java work.
What an Interface Is
An interface is a reference type, similar to a class, but with key restrictions and capabilities. The basic form:
public interface Swimmer {
void swim();
}
That's a complete interface. It declares one method, swim(). There's no body — just the signature. Any class that implements Swimmer must provide an actual swim() method.
public class Duck implements Swimmer {
@Override
public void swim() {
System.out.println("Duck paddling");
}
}
public class Fish implements Swimmer {
@Override
public void swim() {
System.out.println("Fish gliding through water");
}
}
Both classes implement the same interface but provide different implementations. Code working with Swimmer doesn't care which concrete class it has:
Swimmer s = new Duck();
s.swim(); // "Duck paddling"
s = new Fish();
s.swim(); // "Fish gliding through water"
This is the same polymorphism we saw with inheritance (Topic 8.6), but achieved through interface implementation rather than class extension.
Interactive animation: Task implements Comparable, so Collections.sort and a TreeSet - code from 1998 - sort a class written today.
Why Interfaces Exist
Inheritance has limitations. Java is single-inheritance — a class can extend only ONE parent (Topic 8.11). But classes often need to participate in multiple type relationships. A Duck IS-A Bird (extends inheritance), but it CAN swim AND fly AND quack. Without interfaces, you couldn't express all of these.
Interfaces solve four specific problems:
1. Multiple type membership. A class can implement many interfaces, gaining membership in multiple type hierarchies:
public class Duck extends Bird implements Swimmer, Flyer, Quacker { }
Duck IS-A Bird (single inheritance) AND can be treated as a Swimmer AND as a Flyer AND as a Quacker. Different code can interact with the same Duck through different abstractions.
2. Decoupling. Code can depend on an interface without knowing the concrete implementation:
public class WaterRace {
public void race(Swimmer s1, Swimmer s2) {
s1.swim();
s2.swim();
}
}
// Works with any Swimmer:
race(new Duck(), new Fish());
race(new Whale(), new Human());
The WaterRace class doesn't know about Duck, Fish, Whale, or Human. It only knows about Swimmer. New classes can implement Swimmer without WaterRace changing. This is the Dependency Inversion Principle — depend on abstractions, not concretions.
3. Capability description. Interfaces describe what a class CAN DO, not what it IS:
public interface Comparable<T> { int compareTo(T other); }
public interface Iterable<T> { Iterator<T> iterator(); }
public interface AutoCloseable { void close(); }
Any class can implement Comparable if its instances can be compared. Any class can implement Iterable if its instances can be iterated. The class's "what it is" remains separate from its capabilities.
4. Mock-friendliness for testing. Code that depends on interfaces is easy to test — you can substitute mock implementations:
public class UserService {
private final UserRepository repo;
public UserService(UserRepository repo) {
this.repo = repo;
}
}
// In production:
new UserService(new DatabaseUserRepository());
// In tests:
new UserService(new MockUserRepository());
If UserService depended on a concrete DatabaseUserRepository, you couldn't easily test it without a real database. With the interface, swap in a mock. This is one of the most important patterns in modern Java.
Interactive animation: An OrderService that builds its own StripeGateway beside one that takes any PaymentGateway through its constructor.
Interface Syntax
The basic syntax:
[modifiers] interface Name [extends OtherInterfaces] {
// method declarations
// constants
// default methods (Java 8+)
// static methods (Java 8+)
// private methods (Java 9+)
}
public interface Speaker {
void speak(); // abstract method
String LANGUAGE = "English"; // constant (implicit public static final)
default void greet() { ... } // default method (Java 8+)
static Speaker createDefault() { ... } // static method (Java 8+)
private void helper() { ... } // private method (Java 9+)
}
We cover each of these in detail in subsequent Topics. For now, the key point: an interface is a "contract" — a set of methods and constants that implementing classes must provide or inherit.
Interactive animation: The module's route - the original interface, the Java 8 and 9 additions, combining them, then modern use.
What Interfaces Are NOT
Interfaces are NOT classes. They have important restrictions:
- No instance state. Interfaces cannot have instance fields (only
public static finalconstants). - No constructors. You can't
new Swimmer(). Interfaces aren't instantiable directly. - No instance initialization. No instance initializer blocks, no fields to initialize.
These restrictions are intentional — they're what makes the multiple-interface-implementation pattern work safely (Topic 8.11 covered why multiple-class-inheritance is forbidden).
Modern Java has blurred the line a little — interfaces now have default methods (Java 8+), static methods (Java 8+), and private helpers (Java 9+). But they still can't have instance state. That fundamental restriction remains.
A Real Production Example
At AWS S3, the storage operations framework defines a small set of interfaces that virtually all our code implements:
public interface RetryableOperation<T> {
T execute() throws TransientException;
int maxRetries();
Duration backoffStrategy(int attemptNumber);
}
public interface MetricsReporter {
void recordSuccess(String operationName, Duration latency);
void recordFailure(String operationName, String reason);
}
public interface AuditableAction {
String getActionType();
String getResourceId();
String getRequestor();
}
Each interface captures one aspect of "what an operation does." A class implementing several of these interfaces gets all those capabilities:
public class S3GetObjectOperation implements RetryableOperation<S3Object>,
MetricsReporter,
AuditableAction { ... }
The framework code processes operations through these interfaces. It doesn't know about S3GetObjectOperation specifically — it knows about RetryableOperation and MetricsReporter. The same framework handles thousands of operation types because they all implement the same small set of interfaces.
This is the power of interfaces at scale. Add a new operation type? Implement the interfaces. The framework handles it automatically.
When to Use Interfaces vs Classes
A common design question.
Use interfaces when:
- Defining a contract that multiple unrelated classes will implement
- You want to support multiple implementations of the same behavior
- You need polymorphism without inheritance
- The "capability" describes what a class can DO (Swimmer, Comparable, Closable)
- You're writing API contracts
Use classes when:
- You need to share implementation (concrete methods, fields)
- You're modeling an IS-A relationship with shared state
- The hierarchy is shallow and stable
- Concrete instances need to be created directly
In modern Java, interfaces are usually the right starting point. The pattern is: design the interface contract first, then provide one or more class implementations. This is "code to an interface" — a foundational design principle.
graph TD
A["Need polymorphism?"] --> B{"Multiple classes,<br/>related concept?"}
B -->|"Yes, all subtype of one thing"| C["Inheritance<br/>(extends)"]
B -->|"Yes, capability across<br/>different classes"| D["Interface<br/>(implements)"]
B -->|"Both"| E["Class hierarchy<br/>+ interfaces"]
C --> F["Dog IS-A Animal"]
D --> G["Duck, Fish, Whale<br/>all CAN-DO swim"]
E --> H["Duck extends Bird<br/>implements Swimmer, Flyer"]
style C fill:#7c3aed,color:#fff
style D fill:#10b981,color:#fff
style E fill:#3b82f6,color:#fff
This diagram shows when to use interfaces versus inheritance. Use inheritance when you have multiple classes that are all subtypes of one underlying concept — Dog, Cat, Bird are all Animal subtypes. Use interfaces when you have a capability that's shared across otherwise-unrelated classes — Duck, Fish, and Whale aren't related by inheritance, but they all can swim. Often you need both: a class extends one parent class for its primary identity and implements multiple interfaces for additional capabilities. Modern Java code uses interfaces heavily — frameworks, libraries, and APIs are typically defined against interfaces with concrete classes plugging in.
Coding to an Interface
A key pattern in modern Java: declare variables, parameters, and return types as INTERFACE types when possible:
// Bad — exposes implementation
public ArrayList<String> getNames() { return list; }
public void process(ArrayList<String> names) { ... }
// Good — exposes interface only
public List<String> getNames() { return list; }
public void process(List<String> names) { ... }
The second version is more flexible. Callers can pass any List implementation (ArrayList, LinkedList, ImmutableList). The return type can change from ArrayList to a different List without breaking callers.
This is "program to an interface, not an implementation" — one of the design principles from the Gang of Four design patterns book. We see it everywhere in modern Java code.
Common Pitfalls
Warning: Treating interfaces as classes. Interfaces can't have instance state or constructors. They define contracts, not implementations (until Java 8 default methods, which we cover in Topic 9.4).
Forgetting interfaces are reference types. You can have a variable typed as an interface:
Swimmer s = new Duck();. This is the foundation of interface-based polymorphism.Confusing interfaces with abstract classes. Both are abstractions, but they have different rules. Topic 9.12 covers the distinction.
Designing fat interfaces. Interfaces with 20+ methods burden every implementer. Prefer small, focused interfaces (Interface Segregation Principle).
Premature interface extraction. Don't add an interface for every class "just in case." Extract interfaces when there's a real need for multiple implementations or mockability.
Why This Matters in Interviews
Interview: Interface questions appear in nearly every Java interview. "What's the difference between interfaces and abstract classes?" "When would you use an interface vs inheritance?" "What are the rules for interface methods?"
Modern Java questions (Java 8+) probe default methods, functional interfaces, and how interfaces interact with lambdas. Topic 9.13 covers the Interview Lens for the full module.
Try It
Exercise: Design each:
- An interface
Printablewith a methodprint(). Three classes (Book, Receipt, Photo) implement it differently.- A method that accepts any Printable. Pass each of the three classes — verify polymorphism works.
- Try to instantiate Printable directly with
new Printable()— observe the compile error.- Try to add a field to the interface — see what restrictions apply.
- Implement two interfaces in one class. Verify it works.
Each exercise builds basic interface intuition before we dive into the specifics.