Java Tutorial
🔍

Java Interfaces

Java Interfaces

An interface in Java is a contract — a set of method signatures that any implementing class must provide. When a class implements an interface, it makes a promise: "I will implement every method this interface defines." The calling code holds a reference to the interface type, not the concrete class, so any class that fulfils the contract can be substituted without changing the caller.

Interfaces are Java's mechanism for defining what an object can do without dictating how. They are also the solution to Java's single-class-inheritance limitation — a class can implement any number of interfaces, gaining multiple type identities simultaneously.

What Is an Interface in Java?

An interface is a reference type — like a class — that contains only abstract method signatures, constants, and since Java 8, default and static methods. A class uses the implements keyword to declare that it fulfils an interface's contract. Once it does, objects of that class can be referenced through the interface type.

Key characteristics:

  • ›All methods are public by default
  • ›Abstract methods have no body — implementing classes must provide one
  • ›Fields are implicitly public static final — they are constants
  • ›Interfaces cannot be instantiated
  • ›A class can implement multiple interfaces simultaneously
  • ›An interface can extend multiple other interfaces

Interface Syntax — All Forms

Basic Interface

// File: Printable.java public interface Printable { // Abstract method — no body, implementing class must provide one void print(); // Constant — implicitly public static final int MAX_PRINT_WIDTH = 120; }

Interface with Default and Static Methods (Java 8+)

// File: Auditable.java public interface Auditable { // Abstract — must be implemented String getEntityId(); String getEntityType(); // Default method — has a body, implementing class inherits it automatically default String buildAuditLog(String action) { return "[AUDIT] " + getEntityType() + " | ID: " + getEntityId() + " | Action: " + action + " | At: " + java.time.LocalDateTime.now(); } // Static method — belongs to the interface, called on interface name static String formatTimestamp(java.time.LocalDateTime dt) { return dt.format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); } }

Interface Extending Multiple Interfaces

// File: TrackableEntity.java public interface TrackableEntity extends Printable, Auditable { // Adds methods to the combined contract String getStatus(); default String getFullSummary() { return getEntityType() + " [" + getEntityId() + "] — " + getStatus(); } }

Implementing an Interface

// File: Order.java public class Order implements TrackableEntity { private final String orderId; private final String customerName; private String status; public Order(String orderId, String customerName) { this.orderId = orderId; this.customerName = customerName; this.status = "PLACED"; } // Required by Auditable @Override public String getEntityId() { return orderId; } @Override public String getEntityType() { return "Order"; } // Required by TrackableEntity @Override public String getStatus() { return status; } // Required by Printable @Override public void print() { System.out.println("Order: " + orderId + " | Customer: " + customerName + " | Status: " + status); } public void updateStatus(String newStatus) { System.out.println(buildAuditLog("STATUS_UPDATE → " + newStatus)); this.status = newStatus; } }
// File: InterfaceBasicDemo.java public class InterfaceBasicDemo { public static void main(String[] args) { Order order = new Order("ORD-4521", "Priya Mehta"); // Polymorphic references — same object through different interface lenses Printable printable = order; Auditable auditable = order; TrackableEntity trackable = order; order.print(); // Printable System.out.println(auditable.buildAuditLog("VIEWED")); // default method System.out.println(trackable.getFullSummary()); // default method order.updateStatus("SHIPPED"); order.print(); } }
Output:
Order: ORD-4521 | Customer: Priya Mehta | Status: PLACED
[AUDIT] Order | ID: ORD-4521 | Action: VIEWED | At: 2024-01-15T10:30:00
Order [ORD-4521] — PLACED
[AUDIT] Order | ID: ORD-4521 | Action: STATUS_UPDATE → SHIPPED | At: 2024-01-15T10:30:01
Order: ORD-4521 | Customer: Priya Mehta | Status: SHIPPED

The Order object can be referenced as Printable, Auditable, or TrackableEntity — each view exposes a different subset of the object's capabilities. This is multiple type inheritance through interfaces: one object, multiple contracts fulfilled simultaneously.

Default Methods — Java 8+

A default method has a body in the interface itself. Every implementing class automatically inherits it without writing anything. Implementing classes can override it if needed.

Default methods were introduced in Java 8 to allow interfaces to evolve — adding new methods without breaking every existing implementing class. Before Java 8, adding any method to an interface forced every implementor to add the method or fail to compile.

// File: DeliveryAgent.java public interface DeliveryAgent { String getAgentId(); String getAgentName(); double getCurrentLatitude(); double getCurrentLongitude(); // Default — available to all implementations without writing it default String getLocationSummary() { return getAgentName() + " | Location: (" + getCurrentLatitude() + ", " + getCurrentLongitude() + ")"; } default boolean isWithinRadius(double targetLat, double targetLng, double radiusKm) { double latDiff = Math.abs(getCurrentLatitude() - targetLat); double lngDiff = Math.abs(getCurrentLongitude() - targetLng); double approxDistKm = Math.sqrt(latDiff * latDiff + lngDiff * lngDiff) * 111; return approxDistKm <= radiusKm; } // Static — utility method on the interface itself static String formatCoordinates(double lat, double lng) { return String.format("(%.4f, %.4f)", lat, lng); } }
// File: BikeAgent.java public class BikeAgent implements DeliveryAgent { private final String agentId; private final String agentName; private double latitude; private double longitude; private String vehicleNumber; public BikeAgent(String agentId, String agentName, double latitude, double longitude, String vehicleNumber) { this.agentId = agentId; this.agentName = agentName; this.latitude = latitude; this.longitude = longitude; this.vehicleNumber = vehicleNumber; } @Override public String getAgentId() { return agentId; } @Override public String getAgentName() { return agentName; } @Override public double getCurrentLatitude() { return latitude; } @Override public double getCurrentLongitude() { return longitude; } public void updateLocation(double lat, double lng) { this.latitude = lat; this.longitude = lng; } // Optional — overrides the default only if customisation is needed @Override public String getLocationSummary() { return DeliveryAgent.super.getLocationSummary() // calls interface default + " | Vehicle: " + vehicleNumber; } }
// File: DefaultMethodDemo.java public class DefaultMethodDemo { public static void main(String[] args) { BikeAgent agent = new BikeAgent( "AGT-001", "Rahul Kumar", 12.9716, 77.5946, "KA05AB1234" // Bengaluru coordinates ); System.out.println(agent.getLocationSummary()); // overridden default // Interface static method — called on interface name System.out.println(DeliveryAgent.formatCoordinates( agent.getCurrentLatitude(), agent.getCurrentLongitude())); // Check if agent is within 5 km of delivery point double deliveryLat = 12.9352; double deliveryLng = 77.6245; System.out.println("Within 5 km of delivery: " + agent.isWithinRadius(deliveryLat, deliveryLng, 5.0)); } }
Output:
Rahul Kumar | Location: (12.9716, 77.5946) | Vehicle: KA05AB1234
(12.9716, 77.5946)
Within 5 km of delivery: true

When BikeAgent calls DeliveryAgent.super.getLocationSummary(), it invokes the interface's default implementation and then appends vehicle-specific data. This is how a class extends a default method rather than completely replacing it.

Multiple Interface Implementation

A class can implement any number of interfaces — this is Java's answer to multiple inheritance. Each interface adds a new type identity and a new set of required methods.

// File: Identifiable.java public interface Identifiable { String getId(); }
// File: Priceable.java public interface Priceable { double getPrice(); default double getPriceWithGst() { return getPrice() * 1.18; } }
// File: Searchable.java public interface Searchable { String getSearchKeywords(); default boolean matchesQuery(String query) { return getSearchKeywords().toLowerCase().contains(query.toLowerCase()); } }
// File: Product.java // One class — three interface contracts fulfilled simultaneously public class Product implements Identifiable, Priceable, Searchable { private final String productId; private final String productName; private final String category; private final double price; public Product(String productId, String productName, String category, double price) { this.productId = productId; this.productName = productName; this.category = category; this.price = price; } @Override public String getId() { return productId; } @Override public double getPrice() { return price; } @Override public String getSearchKeywords() { return productName + " " + category + " " + productId; } public String getName() { return productName; } public String getCategory() { return category; } public String getSummary() { return productId + " | " + productName + " | Rs." + price; } }
// File: MultipleInterfaceDemo.java import java.util.List; public class MultipleInterfaceDemo { // Each method accepts a specific interface — Product satisfies all three public static void displayId(Identifiable item) { System.out.println("ID: " + item.getId()); } public static void displayPrice(Priceable item) { System.out.printf("Price: Rs.%.2f | With GST: Rs.%.2f%n", item.getPrice(), item.getPriceWithGst()); } public static void searchProducts(List<Searchable> items, String query) { System.out.println("Searching for: \"" + query + "\""); items.stream() .filter(item -> item.matchesQuery(query)) .forEach(item -> System.out.println(" Found: " + item.getSearchKeywords())); } public static void main(String[] args) { Product laptop = new Product("SKU-001", "Laptop Pro 15", "Electronics", 85000.0); Product mouse = new Product("SKU-002", "Wireless Mouse", "Electronics", 1299.0); Product notebook = new Product("SKU-003", "A4 Notebook 200pg","Stationery", 89.0); // Same object passed to methods expecting different interface types displayId(laptop); displayPrice(laptop); System.out.println(); displayId(mouse); displayPrice(mouse); System.out.println(); searchProducts(List.of(laptop, mouse, notebook), "electronics"); System.out.println(); searchProducts(List.of(laptop, mouse, notebook), "laptop"); } }
Output:
ID: SKU-001
Price: Rs.85000.00 | With GST: Rs.100300.00

ID: SKU-002
Price: Rs.1299.00 | With GST: Rs.1532.82

Searching for: "electronics"
  Found: Laptop Pro 15 Electronics SKU-001
  Found: Wireless Mouse Electronics SKU-002

Searching for: "laptop"
  Found: Laptop Pro 15 Electronics SKU-001

Product is simultaneously Identifiable, Priceable, and Searchable. A method that needs only a price — displayPrice(Priceable) — does not need to know the full Product type. This is the Interface Segregation Principle in practice: callers depend only on the capability they need.

Functional Interfaces

A functional interface is an interface with exactly one abstract method. It can be implemented using a lambda expression or method reference — the lambda expression is the implementation of the single abstract method.

Java 8+ provides the @FunctionalInterface annotation — it tells the compiler to enforce the one-abstract-method rule and report an error if violated.

// File: OrderFilter.java @FunctionalInterface public interface OrderFilter { // Exactly one abstract method — what makes this a functional interface boolean accepts(String orderId, double amount, String status); // Default methods are allowed — they don't count as abstract default OrderFilter and(OrderFilter other) { return (id, amt, st) -> this.accepts(id, amt, st) && other.accepts(id, amt, st); } }
// File: FunctionalInterfaceDemo.java import java.util.List; public class FunctionalInterfaceDemo { record OrderRecord(String orderId, double amount, String status) {} public static List<OrderRecord> filterOrders(List<OrderRecord> orders, OrderFilter filter) { return orders.stream() .filter(o -> filter.accepts(o.orderId(), o.amount(), o.status())) .toList(); } public static void main(String[] args) { List<OrderRecord> orders = List.of( new OrderRecord("ORD-001", 1499.0, "PENDING"), new OrderRecord("ORD-002", 299.0, "SHIPPED"), new OrderRecord("ORD-003", 5999.0, "PENDING"), new OrderRecord("ORD-004", 1800.0, "DELIVERED"), new OrderRecord("ORD-005", 150.0, "PENDING") ); // Lambda implements OrderFilter — no class needed OrderFilter highValue = (id, amt, st) -> amt > 1000.0; OrderFilter pendingOnly = (id, amt, st) -> "PENDING".equals(st); // Compose two filters using the default and() method OrderFilter highValuePending = highValue.and(pendingOnly); System.out.println("High-value orders (> Rs.1000):"); filterOrders(orders, highValue) .forEach(o -> System.out.println(" " + o.orderId() + " | Rs." + o.amount())); System.out.println("\nPending orders only:"); filterOrders(orders, pendingOnly) .forEach(o -> System.out.println(" " + o.orderId() + " | " + o.status())); System.out.println("\nHigh-value + Pending:"); filterOrders(orders, highValuePending) .forEach(o -> System.out.println(" " + o.orderId() + " | Rs." + o.amount() + " | " + o.status())); } }
Output:
High-value orders (> Rs.1000):
  ORD-001 | Rs.1499.0
  ORD-003 | Rs.5999.0
  ORD-004 | Rs.1800.0

Pending orders only:
  ORD-001 | Rs.1499.0
  ORD-003 | Rs.5999.0
  ORD-005 | Rs.150.0

High-value + Pending:
  ORD-001 | Rs.1499.0
  ORD-003 | Rs.5999.0

The lambda (id, amt, st) -> amt > 1000.0 is a compact implementation of OrderFilter — the compiler infers that the lambda body implements accepts(). No anonymous class, no boilerplate. The and() default method demonstrates how functional interfaces can be composed — combining two filters into a new one without any new class.

Interface vs Abstract Class

AspectInterfaceAbstract Class
Keywordinterfaceabstract class
Implemented / Extended byimplementsextends
Multiple inheritanceYes — a class can implement manyNo — a class can extend only one
Abstract methodsYes — all methods are abstract by defaultYes — explicitly declared with abstract
Concrete methodsdefault and static only (Java 8+)Yes — regular methods with bodies
Instance fieldsNo — only public static final constantsYes — any field type
ConstructorsNoYes — called via super() in subclass
Access modifiers on methodspublic only (by default)Any — public, protected, package-private
State (mutable data)NoYes
IS-A relationshipYes — type identityYes — type identity
When to usePure contract, multiple type inheritance, capability definitionPartial implementation shared across subclasses

Interface Method Types — Java 8+

Method TypeSyntaxBodyInherited ByCalled Via
Abstractvoid method();NoneImplementing class — must implementObject reference
Defaultdefault void method() {}YesImplementing class — can overrideObject reference
Staticstatic void method() {}YesNot inheritedInterface name only
Private (Java 9+)private void method() {}YesNot inherited — helper for defaultsOther interface methods only

Interface vs Interface with Default Methods — Before and After Java 8

AspectBefore Java 8Java 8+
Method typesAbstract onlyAbstract, default, static
Evolving an interfaceAdding a method breaks all implementorsAdd as default — no breakage
Code in interfaceNoneDefault and static methods
Functional programmingVerbose anonymous classesLambdas with functional interfaces
Multiple implementationOnly abstract contractsContracts + shared behaviour

Basic Example

public class Main { interface Payable { double calculatePay(); } static class Employee implements Payable { private final double monthlySalary; Employee(double monthlySalary) { this.monthlySalary = monthlySalary; } @Override public double calculatePay() { return monthlySalary; } } static class Freelancer implements Payable { private final double hourlyRate; private final int hoursWorked; Freelancer(double hourlyRate, int hoursWorked) { this.hourlyRate = hourlyRate; this.hoursWorked = hoursWorked; } @Override public double calculatePay() { return hourlyRate * hoursWorked; } } public static void main(String[] args) { Payable[] payables = { new Employee(50000.0), new Freelancer(500.0, 40) }; for (Payable p : payables) { System.out.println(p.getClass().getSimpleName() + " pay: " + p.calculatePay()); } } }

Output

Employee pay: 50000.0
Freelancer pay: 20000.0
  1. ›Payable declares one method, calculatePay(), with no body — it is a contract, not an implementation.
  2. ›Employee and Freelancer each implements Payable and provide their own version of calculatePay().
  3. ›The loop holds each object only through the Payable reference type, yet calling calculatePay() runs the correct class's own logic.
  4. ›Adding a new kind of payable worker later — say a Contractor — needs no change to this loop, only a new class that implements Payable.

Best Practices

Keep interfaces focused on a single capability — Interface Segregation Principle

An interface that combines location, pricing, rating, and scheduling into one forces every implementing class to implement all four — even if some are irrelevant. Four separate interfaces — Locatable, Priceable, Ratable, Schedulable — let each class implement only what it supports. This is the Interface Segregation Principle: no class should be forced to implement methods it does not use.

Name interfaces as capabilities or roles — not as class prefixes

Printable, Comparable, Runnable, Serializable, Iterable — Java's own standard library names interfaces as adjectives or capabilities. Avoid naming interfaces IPaymentGateway or AbstractPrintable — the I prefix and Abstract prefix are unnecessary and uninformative. Name the interface after what objects that implement it can do.

Use @FunctionalInterface when an interface is designed for lambdas

When you intend an interface to be used with lambda expressions, annotate it with @FunctionalInterface. This causes a compile error if anyone accidentally adds a second abstract method, protecting the functional contract. Java's built-in Predicate<T>, Function<T,R>, Consumer<T>, and Supplier<T> all use this annotation.

Prefer interfaces over abstract classes for type definitions

When choosing between an interface and an abstract class to define a type, prefer the interface unless the type genuinely needs shared state or shared concrete implementation logic. Interfaces impose fewer restrictions — implementing classes can still extend a class of their choice. Abstract classes consume the single inheritance slot. Give implementing classes maximum freedom by using interfaces.

Common Mistakes

Mistake 1 — Trying to Add State to an Interface

public interface Counter { int count = 0; // implicitly public static final — this is a constant, not state void increment(); int getCount(); } // Every implementing class will share the same count = 0 constant // Modifying count through an implementation does NOT change the interface's constant // The implementing class needs its own instance field for actual state

Interface fields are constants — they cannot be used as instance state shared or modified by implementing objects. Every field in an interface is implicitly public static final. For mutable state that implementing classes need, each class must declare its own instance fields.

Mistake 2 — Implementing an Interface Without Providing All Abstract Methods

public interface Reportable { String generateReport(); void saveReport(String path); } // Compile error — saveReport() not implemented public class QuickReport implements Reportable { @Override public String generateReport() { return "Quick report content"; } // Missing saveReport() implementation — compiler error }
Compile error: QuickReport is not abstract and does not override abstract method saveReport(String) in Reportable

Every abstract method in every implemented interface must have an implementation in the concrete class. If the class cannot provide all implementations, it must be declared abstract itself — allowing a concrete subclass to complete the contract.

Mistake 3 — Default Method Conflict Without Resolution

public interface InterfaceA { default void display() { System.out.println("InterfaceA display"); } } public interface InterfaceB { default void display() { System.out.println("InterfaceB display"); } } // Compile error — which display() should be used? public class ConflictDemo implements InterfaceA, InterfaceB { // Must override display() to resolve the conflict // Without override: "class ConflictDemo inherits unrelated defaults" }
// Correct resolution public class ConflictDemo implements InterfaceA, InterfaceB { @Override public void display() { InterfaceA.super.display(); // explicitly pick one — or write a new body System.out.println("ConflictDemo resolves the conflict"); } }

When two interfaces provide default methods with the same signature, the implementing class must override the method and explicitly resolve which version to use — or provide an entirely new implementation. The compiler enforces this.

Mistake 4 — Using an Interface as a Constant Container

// Anti-pattern — constants-only interface (Constant Interface Anti-Pattern) public interface AppConstants { String BASE_URL = "https://api.example.com"; int TIMEOUT_MS = 5000; String API_VERSION = "v2"; } // Classes implement it just to use the constants without class-name prefix public class PaymentService implements AppConstants { public void callApi() { System.out.println(BASE_URL + "/" + API_VERSION); // works but wrong } }

Implementing an interface solely to avoid writing AppConstants.BASE_URL everywhere is an anti-pattern. It pollutes the class's type hierarchy — PaymentService is not an AppConstants. Use a public final class with public static final fields and a private constructor instead: AppConfig.BASE_URL.

Interview Questions

Q1. What is an interface in Java and how is it different from a class?

An interface is a reference type that defines a contract — a set of method signatures that implementing classes must provide. A class defines concrete types with state and behaviour. A class can implement multiple interfaces but extend only one class. Interfaces cannot be instantiated, cannot hold instance fields or constructors, and before Java 8 could only hold abstract methods. After Java 8, they can hold default and static methods. A class is a full implementation; an interface is a contract.

Q2. What are default methods in Java interfaces and why were they introduced?

Default methods are methods in an interface that have a body — implementing classes inherit them automatically without writing any code. They were introduced in Java 8 specifically to allow interfaces to evolve without breaking existing implementing classes. Before Java 8, adding any method to an interface forced every implementing class to add that method or fail to compile — impossible when classes exist in libraries you do not control. Default methods provide backwards compatibility for interface evolution. Implementing classes can override them when needed.

Q3. What is the difference between an interface and an abstract class in Java?

An interface defines a pure contract — only method signatures (and since Java 8, default and static methods). It has no instance fields, no constructors, and supports multiple implementation. An abstract class can have concrete methods, instance fields, and constructors, but a class can only extend one. Use an abstract class when subclasses share concrete implementation or state. Use an interface for a pure contract, for defining capability roles, or when multiple type inheritance is needed. Most Java design guidelines favour interfaces over abstract classes for type definitions.

Q4. What is a functional interface and how does it relate to lambda expressions?

A functional interface is an interface with exactly one abstract method. The @FunctionalInterface annotation enforces this constraint at compile time. Lambda expressions are a shorthand for implementing a functional interface — the lambda body implements the single abstract method. Java 8's standard functional interfaces (Predicate<T>, Function<T,R>, Consumer<T>, Supplier<T>) are all functional interfaces. Functional interfaces are the bridge between Java's object-oriented type system and functional programming style.

Q5. What happens when two interfaces that a class implements have a default method with the same signature?

The implementing class must override the conflicting method to resolve the ambiguity — the compiler enforces this with an error. Inside the overriding method, the class can call either interface's default using InterfaceA.super.method() or InterfaceB.super.method(), or provide an entirely new body. This is the diamond problem equivalent for interface default methods — Java resolves it by requiring the implementing class to make the choice explicit.

Q6. Can an interface extend another interface in Java?

Yes. An interface can extend one or more other interfaces using the extends keyword — and it can extend multiple interfaces simultaneously (unlike classes). The extending interface inherits all abstract methods of the parent interfaces and can add new ones. Any class implementing the child interface must implement all abstract methods from the entire inheritance chain. Interfaces cannot implement other interfaces — only extend them.

FAQs

What is an interface in Java?

An interface is a reference type that defines a contract — a set of abstract method signatures that implementing classes must provide. It cannot be instantiated, cannot hold mutable state, and since Java 8 can include default and static methods with bodies. A class declares it fulfils an interface using implements.

Can an interface have a constructor in Java?

No. Interfaces cannot have constructors because they cannot be instantiated. State initialisation happens in the implementing class's own constructor.

Can an interface have concrete methods?

Yes, since Java 8. default methods have a body and are inherited by implementing classes. static methods have a body and are called on the interface name. Since Java 9, interfaces can also have private methods — used as helpers within the interface's own default methods.

What is the difference between implements and extends in Java?

A class uses implements to fulfil an interface's contract — multiple interfaces are allowed. A class uses extends to inherit from another class — only one is allowed. An interface uses extends to inherit from other interfaces — multiple are allowed. These keywords convey the relationship type: implements is "I provide this contract" and extends is "I am a specific kind of this."

What is the Interface Segregation Principle?

The Interface Segregation Principle states that no class should be forced to implement methods it does not use. Large interfaces should be split into smaller, more specific ones. A class should implement only the interfaces relevant to its capabilities. This prevents classes from having empty or unsupported method implementations.

Can an interface extend a class in Java?

No. An interface cannot extend a class. An interface can only extend other interfaces. Classes can implement interfaces. This one-way relationship is enforced by the language.

Summary

Interfaces are Java's contracts — they define what objects can do without prescribing how. A class that implements an interface makes a binding promise to provide every abstract method the interface declares. Since Java 8, default methods allow interfaces to evolve and provide shared behaviour, and functional interfaces enable lambda expressions as concise implementations.

The practical power of interfaces is twofold: they allow multiple type inheritance — one class can fulfil many contracts simultaneously — and they decouple callers from implementations entirely. Code written against PaymentGateway works with Razorpay, PayU, Stripe, or any future gateway without a single change. That replaceability is what interfaces exist to provide.

For interviews, be ready to explain the difference between interfaces and abstract classes clearly, describe the purpose of default methods and when they were introduced, demonstrate how a class implements multiple interfaces, and define a functional interface with a lambda example. These points appear consistently in both service-based recall and product-based design discussions.

What to Read Next

Practice Programs

Put this topic into practice — each program has full code, output and a step-by-step explanation you can run online.

Browse all Java programs →