Interfaces in Java
Interfaces as contracts: implicit modifiers, multiple implementation and interface inheritance, marker interfaces versus annotations, private methods, the constant interface anti-pattern, programming to an interface, and invokeinterface cost.
On this page
🔌 What is an interface?
An interface is a named contract: a list of operations a type promises to support, without saying how. A class signs the contract with implements and must then provide every abstract method. For example, a PaymentGateway interface with charge(orderId, amount) lets your checkout code work with Stripe, Razorpay or a test fake, as long as each one implements that single method.
Interviewers ask about interfaces at every level. A junior screen checks that you know the implicit modifiers and that a class can implement many interfaces. A senior round asks why Serializable has no methods, why ArrayList implements RandomAccess, what a constant interface is and why it's an anti-pattern, and whether calling through an interface is slower than calling through a class. The good news is that all of those answers come from one idea: an interface defines a type, and Java lets a class have many types.
What you’ll learn
- The implicit modifiers: methods are
public abstract, fields arepublic static final - How a class implements many interfaces and an interface extends many, and why state stays out
- What marker interfaces (
Serializable,Cloneable,RandomAccess) do, and when to prefer an annotation - Why Java 9 added
privateinterface methods, and why the constant interface is an anti-pattern - How to program to an interface so implementations are swappable and testable
- How
invokeinterfaceand itables work, and why the JIT makes them nearly free
For this lesson, we'll focus on what an interface declares and the modifiers the compiler adds for you, multiple implementation and interface inheritance, marker interfaces versus annotations, private interface methods, the constant interface anti-pattern, programming to an interface, and what invokeinterface costs at runtime.
Below the line (out of scope)
defaultandstaticmethods and their resolution rules: covered in Static and default methods in interfaces- Single-abstract-method interfaces and lambdas: covered in Functional interfaces in Java
- Closed hierarchies with
sealedinterfaces: covered in Sealed classes and interfaces
The 30-second answer
An interface is a contract that defines a type without fixing an implementation. Its abstract methods are implicitly public abstract, its fields are implicitly public static final, and it has no constructors or instance state. A class can implement any number of interfaces, and an interface can extend many others, which is how Java gets multiple inheritance of type without multiple inheritance of state. Since Java 8 interfaces can have default and static methods, and since Java 9 private ones. Marker interfaces like Serializable have no methods and exist only so code can check the type. I declare variables and parameters as the interface (List, not ArrayList) so implementations stay swappable, and calls compile to invokeinterface, which the JIT usually makes as cheap as a normal virtual call.
Interview radar
Asked very often: Expect questions about implicit public abstract methods, public static final fields, why a class can implement many interfaces, and when to declare List instead of ArrayList.
Senior rounds probe: Senior rounds probe invokeinterface dispatch, marker interfaces versus annotations, private interface helpers, and the constant interface anti-pattern.
Production angle: In production this shows up as dependency injection boundaries, swappable repositories and API leaks when an interface is used for constants instead of a real type.
01Picture it first: sockets on a wall
Think of the power sockets in a hotel room. The socket is the contract: a shape and a voltage. A phone charger, a laptop brick and a hair dryer are completely different devices, but each has a plug that fits, so the room works with all of them. The socket doesn't care what's on the other end. And one device can carry several plugs, like a travel adapter that fits both the wall and a USB port.
That's the relationship between interfaces and classes. The figure below shows one gateway class carrying three "plugs": FullGateway (which itself combines PaymentGateway and Refundable) and Auditable. Any code that needs one of those types can accept a StripeGateway.
StripeGateway implements FullGateway and Auditable; FullGateway extends both PaymentGateway and Refundable, so a StripeGateway object can be passed anywhere any of the four interface types is expected
Notice two kinds of arrows. A class implements interfaces, and an interface extends other interfaces (as many as it likes). There's no arrow for state, because interfaces carry none. That's why Java can allow this "many parents" picture for interfaces while allowing only one superclass.
02Interfaces under the hood
Try it yourself first
Before reading on, look at the interface below and write down the full modifiers the compiler gives MAX_RETRIES and charge. Then decide whether StripeGateway may declare charge without public.
public interface PaymentGateway {
int MAX_RETRIES = 3; // really: public static final
String charge(String orderId, BigDecimal amount); // really: public abstract
}
interface Refundable { void refund(String txnId); }
interface Auditable { String auditTag(); }
interface FullGateway extends PaymentGateway, Refundable {} // multiple interface inheritance
class StripeGateway implements FullGateway, Auditable {
@Override public String charge(String orderId, BigDecimal amount) { return "txn-" + orderId; }
@Override public void refund(String txnId) { /* call Stripe */ }
@Override public String auditTag() { return "stripe"; }
}
PaymentGateway g = new StripeGateway();
System.out.println(g instanceof Refundable); // prints: true
System.out.println(Modifier.toString(
PaymentGateway.class.getField("MAX_RETRIES").getModifiers())); // prints: public static final
Notice four things:
- Methods without a body are
public abstract. Writing those words is legal but redundant. Writingprotectedorfinalon an interface method is a compile error. - Fields are
public static final, always. There's no way to declare an instance field in an interface, and every field needs an initialiser. The interface itself is implicitlyabstract, and nested types inside it are implicitlypublic static. - Implementations must be
public. IfStripeGatewaywritesString charge(...)withoutpublic,javacreportsattempting to assign weaker access privileges; was public. That's the overriding rule from Access modifiers. instanceofsees every interface in the graph.gis declared asPaymentGateway, but the object is also aRefundable,FullGatewayandAuditable. The variable's type limits what you can call; the object's class decides what it is.
What an interface can and can't contain
Since Java 9, an interface can hold more than signatures. The full list is: abstract methods, default methods (Java 8), static methods (Java 8), private and private static methods (Java 9), constants, and nested types. What it can never have is instance fields, constructors, instance initialiser blocks, or protected and final methods. Those all relate to per-object state or to controlling subclasses, which is the job of an abstract class.
private methods arrived with JEP 213 to solve a practical problem: two default methods that need the same helper. Before Java 9, the helper had to be a default or static method, which made it part of the public API. Now it stays hidden:
public interface PaymentGateway {
int MAX_RETRIES = 3;
String charge(String orderId, BigDecimal amount);
default String chargeWithRetry(String orderId, BigDecimal amount) {
for (int attempt = 1; ; attempt++) {
try {
return charge(orderId, amount);
} catch (RuntimeException e) {
if (attempt == MAX_RETRIES) throw e;
logFailure(attempt, e);
}
}
}
private void logFailure(int attempt, RuntimeException e) { // Java 9+: not part of the API
System.out.println("attempt " + attempt + " failed: " + e.getMessage());
}
}
// With a gateway that times out twice: prints "attempt 1 failed: timeout",
// "attempt 2 failed: timeout", then returns "txn-o-7"
A private interface method can't be abstract or default, and callers outside the interface can't see it. We cover the default-method rules properly in the next lesson.
Marker interfaces: a type with no methods
Serializable, Cloneable and RandomAccess declare zero methods. They exist so code can ask "is this object one of those?" and change behaviour:
Serializable:ObjectOutputStream.writeObjectchecksinstanceof Serializableand throwsNotSerializableExceptionif the check fails. Writing a plainSessionobject fails withjava.io.NotSerializableException: Session.Cloneable:Object.clone()checks it and throwsCloneNotSupportedExceptionotherwise. The interface doesn't even declareclone(), which is one reason Effective Java calls it a broken design.RandomAccess: tells generic algorithms thatget(i)is fast.Collections.binarySearchuses an indexed search for anArrayList(trueforinstanceof RandomAccess) and an iterator-based search for aLinkedList(false), becauseget(i)on a linked list walks the nodes and would make binary search O(n log n).
Java 5 annotations can also mark things, so when do you pick which? A marker interface defines a type, so the compiler can check it: a method can take a Serializable parameter and reject everything else at compile time. An annotation can't do that, but it can mark methods, fields and packages (not only classes), can carry values (@Retention(RUNTIME), @Table(name = "orders")), and can be added without changing the type hierarchy. My rule: if you'd ever want a method parameter of that type, use a marker interface; otherwise use an annotation.
03Real world: programming to an interface
Try it yourself first
Before reading on, look at OrderService and list what you'd have to change to swap the in-memory store for a database, or to test it without a database.
// Coupled: depends on a concrete class
class OrderService {
private final HashMap<String, Order> store = new HashMap<>();
ArrayList<Order> findByUser(String userId) { /* ... */ return new ArrayList<>(); }
}
// Programmed to interfaces: depends only on contracts
interface OrderRepository {
Optional<Order> findById(String id);
List<Order> findByUser(String userId);
void save(Order order);
}
class OrderService2 {
private final OrderRepository repo; // any implementation
OrderService2(OrderRepository repo) { this.repo = repo; }
List<Order> recentOrders(String userId) { // return the interface type
return repo.findByUser(userId);
}
}
The second version is what "program to an interface, not an implementation" means. OrderService2 compiles against OrderRepository and List, so you can pass a JdbcOrderRepository in production, an InMemoryOrderRepository in tests, and a caching decorator later, without touching the service. Spring's dependency injection, JDBC's Connection and PreparedStatement, and the whole Collections framework are built on this idea. Declaring List<Order> orders = new ArrayList<>() instead of ArrayList<Order> orders is the everyday version of the same habit.
The diagram below shows the design shape I want you to picture when code is programmed to interfaces instead of concrete classes.
OrderService depends on an OrderRepository interface while JDBC, in-memory and cached repositories can be swapped underneath without changing the service
The constant interface anti-pattern still shows up in older codebases: interface DbConstants { String ORDERS_TABLE = "orders"; int BATCH = 500; }, and then class OrderDao implements DbConstants only to write BATCH without a prefix. That implements leaks into your public API (every subclass and every instanceof now sees it), and the JDK itself carries the scar in java.io.ObjectStreamConstants. There's a second, nastier bug: interface constants are compile-time constants, so javac copies 500 into every class that uses it. I've seen a team change BATCH to 200, redeploy only the jar with the interface, and keep running with 500 for weeks. Put constants in a final class with a private constructor (or an enum), and use import static if you want the short name.
04Advanced: what invokeinterface costs, and other edge cases
invokevirtual vs invokeinterface
A call through a class type compiles to invokevirtual, and a call through an interface type to invokeinterface. The difference matters because of how HotSpot lays out method tables. Every class has a vtable, where a method keeps the same index in every subclass, so dispatch is "load the receiver's class, load slot k, jump". Interfaces can't promise a fixed index, because a class can implement any combination of them in any order. So each class also has an itable: a small table of "interface → where its methods start" entries, followed by the method blocks.
The figure below compares the two lookups for a call site the JIT couldn't optimise.
Two dispatch paths: invokevirtual loads the receiver class and reads a fixed vtable slot in one step, while invokeinterface loads the receiver class, scans the itable offset entries until it finds the interface, then reads the method at that interface's block plus an index
That scan sounds slow, and it's why "interfaces are slower" is a common interview claim. In practice the JIT rarely reaches it:
At a monomorphic call site (one receiver class seen), HotSpot uses an inline cache: one class-pointer compare, then a direct call that's usually inlined, so invokeinterface costs the same as invokevirtual, often zero. A bimorphic site (two classes) gets two compares. Only a megamorphic site (three or more classes) falls back to a dispatch stub: two dependent loads for a vtable call, versus those loads plus a short linear scan for an itable call. In microbenchmarks that's typically one to two nanoseconds for the vtable path and a few nanoseconds for the itable path. If only one implementation of an interface is loaded, class hierarchy analysis can devirtualise the call entirely. Design for clarity; this is almost never your bottleneck.
The place I have seen interface checks really hurt is instanceof and casts against an interface on a hot path across many threads. HotSpot answered those with a one-element "secondary super cache" per class, and threads checking against different interfaces kept overwriting it, causing cache-line ping-pong between cores. JDK 23 replaced that cache with a hashed lookup table (JDK-8180450). On Java 21, if a profiler shows heavy time in type checks of a hot class, this is the likely culprit.
Interface initialisation is lazy
A class's initialisation triggers its superclass's initialisation, but not its superinterfaces' (unless they declare default methods). Interface fields that aren't compile-time constants are only initialised when first used:
interface RegionConfig {
String REGION = loadRegion(); // not a compile-time constant
static String loadRegion() { System.out.println("RegionConfig initialised"); return "ap-south-1"; }
}
class BillingService implements RegionConfig {
static { System.out.println("BillingService initialised"); }
}
new BillingService(); // prints: BillingService initialised
System.out.println(RegionConfig.REGION); // prints: RegionConfig initialised, then ap-south-1
Quick rules that come up as follow-ups
| Question | Answer |
|---|---|
| Can an interface extend a class? | No, only other interfaces (any number) |
| Can an interface implement another interface? | No: interfaces extends, classes implements |
Can you new an interface? |
No, but new Runnable() { ... } creates an anonymous class that implements it |
Can an interface method be final, protected or synchronized? |
No |
| Can an abstract class skip interface methods? | Yes; the first concrete subclass must implement them |
| Two interfaces declare the same abstract method? | One implementation satisfies both |
| Same constant name in two interfaces? | Legal until you use it unqualified: reference to X is ambiguous |
Many candidates answer "what's the difference between an abstract class and an interface?" with "interfaces can't have method bodies". That stopped being true in Java 8, and saying it tells the interviewer your knowledge stopped there too. Lead with the differences that still hold: interfaces have no instance state and no constructors, a class can implement many of them, and all their non-private members are public.
05How to say it in the interview
When I ask about interfaces, I'm listening for whether you think of an interface as a type and a contract, not as "a class with empty methods". I want the implicit modifiers stated precisely, the modern member list (default, static, private), a good reason for marker interfaces, and a practical habit: declaring List, injecting a repository interface. Senior candidates can also explain why interface dispatch is cheap after JIT and when it isn't.
In the interview
"An interface defines a type as a contract with no instance state. Abstract methods are implicitly public abstract, fields are public static final, and since Java 8 and 9 it can also have default, static and private methods. A class can implement many interfaces and an interface can extend many, which gives us multiple inheritance of type without the state problems. Marker interfaces like Serializable or RandomAccess have no methods; libraries check them with instanceof, and I prefer them over annotations when I want the compiler to enforce the type. I avoid constant interfaces because they leak into the API and constants get inlined. I program to interfaces so implementations are swappable and testable. Calls compile to invokeinterface; at runtime the JIT uses inline caches, so it's as cheap as a virtual call except at megamorphic sites, where the itable scan costs a few nanoseconds."
On the whiteboard
interface = type + contract, no instance state, no ctor
method (no body) → public abstract field → public static final
+ default/static (8), private (9) never protected/final
class implements A, B, C interface extends A, B
impl methods must be public
markers: Serializable, Cloneable, RandomAccess → instanceof checks
marker iface = compile-time type; annotation = metadata, any element
constant interface = anti-pattern (API leak + inlined constants)
program to interface: List<Order> x = new ArrayList<>()
invokeinterface → itable scan, but inline cache / CHA → ~free
Further reading (8)
- JLS §9 Interfaces
- JVMS §6.5 invokeinterface
- JEP 213: Milling Project Coin (private interface methods)
- The Java Tutorials: Interfaces
- Javadoc: java.util.RandomAccess
- Effective Java, 3rd ed., Items 22 and 41: Use interfaces only to define types; Use marker interfaces to define types
- JDK-8180450: secondary_super_cache does not scale well
- Baeldung: A Guide to Java Interfaces