Search⌘ K
AI Features

Interfaces

Explore Java interfaces to understand how they define method contracts enabling unrelated classes to share capabilities. Learn to implement multiple interfaces, use default methods for API evolution, and distinguish interfaces from abstract classes to design flexible, maintainable applications.

We can use abstract classes to share code and define templates for related objects. This works perfectly when objects share a strict is-a relationship, like a Dog being an Animal. But often we need to define a capability shared by objects that are completely unrelated. Consider a bird and an airplane: both can fly, but they do not belong to the same family tree. If we tried to force them under a common Flyer superclass, we would create a messy, confusing hierarchy.

Java solves this with interfaces, a mechanism that defines what an object can do, regardless of its type. By using interfaces, we create flexible contracts that decouple our code from specific implementation details.

Defining a contract with interfaces

An interface is a contract that specifies a set of methods that a class must implement. Unlike a class, an interface does not describe how behavior is performed; it only describes what that behavior looks like.

We define an interface using the interface keyword. Inside, we declare methods without bodies. By default, these methods are implicitly public and abstract. We do not need to type these keywords, though knowing they are there helps us understand the rules.

Interfaces can also contain constant values. By rule, any java interface variable you declare is implicitly public, static, and final.

Here is how we define a simple contract for objects that can be saved to a database:

C++
// Defining the contract
interface Persistable {
// Implicitly public static final
String DB_NAME = "MAIN_DB";
// Implicitly public abstract
void save();
// Implicitly public abstract
void delete();
}
  • Line 2: We use the interface keyword to define a contract that specifies what a class must do, rather than how it does it.

  • Line 4: We declare a constant field. Even without keywords, it is static and final.

  • Lines 7 & 10: We declare method signatures ending with a semicolon. We provide no braces or logic.

Implementing interfaces

Once we define an interface, classes must sign the contract using the implements keyword. When a class implements an interface, it promises to provide an implementation for every abstract method defined in that interface.

If a concrete class fails to implement even one method from the interface, the Java compiler will generate an error. This ensures that any object treated as Persistable is guaranteed to have working save() and delete() methods.

Here, we see two unrelated classes implementing the same contract:

Java 25
interface Persistable {
void save();
}
class User implements Persistable {
private String username;
public User(String username) {
this.username = username;
}
@Override
public void save() {
System.out.println("Saving user " + username + " to the User Table.");
}
}
class FileDocument implements Persistable {
private String filePath;
public FileDocument(String filePath) {
this.filePath = filePath;
}
@Override
public void save() {
System.out.println("Writing document metadata for " + filePath + " to disk.");
}
}
public class InterfaceExample {
public static void main(String[] args) {
Persistable p1 = new User("Alice");
Persistable p2 = new FileDocument("/docs/report.pdf");
p1.save();
p2.save();
}
}
  • Lines 1–3: We define a functional contract Persistable with a single method.

  • Line 5: The User class signs the contract using implements Persistable.

  • Lines 12–15: User provides its own specific logic for save().

  • Line 18: FileDocument also implements Persistable. It shares no hierarchy with User.

  • Lines 25–28: FileDocument provides totally different saving logic tailored to files.

  • Lines 33–34: We can treat both objects as Persistable, referencing them by their interface type rather than their class type.

Multiple inheritance of type

One of Java’s strict rules is that a class can extend only one superclass. This avoids the Diamond Problem, where a class inherits conflicting state (like two variables named x) from two different parents.

However, Java allows a class to implement multiple interfaces. Since interfaces generally define abstract behavior without state, there is no risk of conflicting variables. This allows us to combine capabilities. A SmartPhone might be a Phone, but it is also a Camera, a MusicPlayer, and GPS.

We separate interfaces with commas:

Java 25
interface Camera {
void takePhoto();
}
interface GPS {
void getLocation();
}
class SmartPhone implements Camera, GPS {
@Override
public void takePhoto() {
System.out.println("Capturing image with 12MP sensor...");
}
@Override
public void getLocation() {
System.out.println("Locating: 40.7128° N, 74.0060° W");
}
}
public class MultiInheritanceExample {
public static void main(String[] args) {
SmartPhone myPhone = new SmartPhone();
myPhone.takePhoto();
myPhone.getLocation();
}
}
  • Lines 1–3: We define the Camera interface, which establishes a contract for any device capable of capturing images.

  • Lines 5–7: We define the GPS interface, which establishes a contract for any device capable of providing geographic coordinates.

  • Line 9: The SmartPhone class uses the implements keyword followed by a comma-separated list to inherit the types of both Camera and GPS.

  • Lines 11–14: We implement the takePhoto() method from the Camera interface. This specific implementation simulates a sensor capture by printing a confirmation message to the console.

  • Lines 16–19: We implement the getLocation() method from the GPS interface. This implementation simulates a coordinate lookup by printing specific latitude and longitude data.

  • Line 24: We instantiate the SmartPhone object. Because it implements both interfaces, the myPhone reference can access all methods defined in the hierarchy.

  • Lines 26–27: We call the implemented methods to verify that the single SmartPhone object successfully performs behaviors from two independent source interfaces.

Evolving interfaces with default methods

Historically, once an interface was published and used by other developers, we could never add a new method to it. Doing so would break every class implementing that interface, because those classes would suddenly be missing an implementation for the new method.

To solve this, modern Java allows interfaces to define default methods. A default method has a body and the default keyword. If an implementing class does not override this method, it simply uses the default version provided by the interface. This feature is primarily used for evolving APIs without breaking existing code.

Java 25
interface Chargeable {
void charge();
// New method added later; does not break existing classes
default void checkBattery() {
System.out.println("Checking battery level... (Default Implementation)");
}
}
class Robot implements Chargeable {
@Override
public void charge() {
System.out.println("Robot is plugging into the docking station.");
}
}
class ElectricCar implements Chargeable {
@Override
public void charge() {
System.out.println("Car connected to Supercharger.");
}
// We can optionally override the default method
@Override
public void checkBattery() {
System.out.println("Battery Health: 98%");
}
}
public class DefaultMethodExample {
public static void main(String[] args) {
Chargeable robot = new Robot();
Chargeable car = new ElectricCar();
robot.checkBattery(); // Uses default logic
car.checkBattery(); // Uses overridden logic
}
}
  • Line 5: We define checkBattery with the default keyword and provide a method body.

  • Line 10: The Robot class implements Chargeable but ignores checkBattery. It compiles successfully and inherits the default behavior.

  • Line 24: The ElectricCar class overrides checkBattery to provide a specialized implementation.

  • Line 35: Calling the method on robot triggers the fallback code defined in the interface.

Interfaces vs. abstract classes

Beginners often struggle to decide between an abstract class and an interface. The decision usually comes down to identity (“is-a”) versus capability (“can-do”).

Feature

Interface

Abstract class

Purpose

Capability contract

Shared base with optional partial implementation

Fields

Only public static final constants

Can have instance fields

Methods

Abstract methods + default methods

Abstract + concrete methods

Inheritance

A class can implement many interfaces

A class can extend one abstract class

Use an abstract class when:

  • You want to share code (state and methods) among closely related classes.

  • You need to define non-static and non-final fields (state).

  • The classes share a clear is-a relationship (e.g., Truck is a Vehicle).

Use an interface when:

  • You want to define a capability for unrelated classes (e.g., Bird and Airplane are both Flyable).

  • You need to take advantage of multiple inheritance (e.g., a class needs to be both Sortable and Serializable).

  • You want to completely decouple your design from specific implementations.

In modern Java development, interfaces are preferred for defining API contracts because they offer greater flexibility.

We can now write code that depends on abstractions instead of concrete implementations. This decoupling enables modular and maintainable system design. By depending on interfaces or abstract contracts, we can replace one implementation with another, such as switching from file-based storage to a database, without changing the core business logic.