Java Interfaces
An interface in Java is a contract: it declares a set of methods that a class promises to implement, without saying how. Interfaces let you write code that depends on what an object can do rather than what class it belongs to, which is the foundation of flexible, decoupled Java design. They are also Java’s answer to multiple inheritance of type, since a class can implement many interfaces but extend only one class.
Overview / How Interfaces Work
A Java interface is declared with the interface keyword instead of class. Historically (before Java 8) an interface could only contain abstract method signatures and constants — no bodies at all. Since Java 8, interfaces can also have default methods (with a body, inherited by implementers) and static methods (utility methods that belong to the interface itself). Since Java 9, interfaces can even have private helper methods used internally by default methods. However, an interface still cannot have instance fields or constructors; any field you declare in an interface is implicitly public static final (a constant), and it has no state of its own.
When a class implements an interface using the implements keyword, it is making a binding promise to the compiler: provide a concrete, public body for every abstract method the interface declares (unless the class itself is abstract). The compiler checks this at compile time — if you forget a method, your code will not compile. This is different from simply hoping a class has a method; the interface guarantees it.
Under the hood, the JVM does not store interface method implementations directly attached to variables of the interface type. Instead, a variable declared as an interface type (for example Shape s = new Circle();) holds a reference to an actual object on the heap, and that object’s real class carries a method table used for dynamic dispatch. When you call s.area(), the JVM looks at the object’s actual runtime class (here, Circle) and invokes that class’s implementation — this is runtime polymorphism, and interfaces are one of its primary vehicles in Java. The interface type itself exists mainly at compile time to restrict what the compiler will let you call through that reference.
Interfaces also enable a class to implement multiple interfaces at once, something Java forbids for classes (single inheritance only). This is safe because interfaces (mostly) don’t carry state, so there is no ambiguity about which superclass’s fields a subclass should inherit — only method contracts are combined.
Syntax
interface InterfaceName {
// constant (implicitly public static final)
int MAX = 100;
// abstract method (implicitly public abstract)
void doSomething();
// default method (has a body, can be overridden)
default void helper() {
System.out.println("default behavior");
}
// static method (belongs to the interface, not instances)
static InterfaceName create() {
return new SomeImplementation();
}
}
class SomeClass implements InterfaceName {
public void doSomething() {
// required implementation
}
}
- interface — the keyword that declares the type.
- abstract methods — no body, ended with a semicolon; every non-abstract implementing class must define these.
- default methods — introduced in Java 8, provide a body so implementing classes inherit behavior without being forced to override it.
- static methods — called on the interface name itself, e.g.
InterfaceName.create(), never on an instance. - implements — the keyword a class uses to fulfill an interface’s contract; a class may implement several interfaces separated by commas.
- constants — fields in an interface are always
public static finaleven if you omit those modifiers.
Examples
Example 1: A basic interface with two implementations
public class Main {
public static void main(String[] args) {
Shape[] shapes = { new Circle(5), new Rectangle(4, 6) };
for (Shape s : shapes) {
System.out.printf("Area: %.2f, Perimeter: %.2f%n", s.area(), s.perimeter());
}
}
}
interface Shape {
double area();
double perimeter();
}
class Circle implements Shape {
private double radius;
Circle(double radius) { this.radius = radius; }
public double area() { return Math.PI * radius * radius; }
public double perimeter() { return 2 * Math.PI * radius; }
}
class Rectangle implements Shape {
private double width, height;
Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
public double area() { return width * height; }
public double perimeter() { return 2 * (width + height); }
}
Output:
Area: 78.54, Perimeter: 31.42
Area: 24.00, Perimeter: 20.00
Here, Shape defines the contract (area() and perimeter()) but has no idea how a circle or rectangle computes them. Each class provides its own formula. The array is declared as Shape[], so the loop body only needs to know the four operations any Shape supports — it works identically no matter how many shape classes you add later.
Example 2: Default and static methods
public class Main {
public static void main(String[] args) {
Vehicle car = () -> System.out.println("Car engine starting...");
car.start();
car.honk();
Vehicle def = Vehicle.createDefault();
def.start();
}
}
interface Vehicle {
void start();
default void honk() {
System.out.println("Beep beep!");
}
static Vehicle createDefault() {
return () -> System.out.println("Default vehicle starting...");
}
}
Output:
Car engine starting...
Beep beep!
Default vehicle starting...
Vehicle has exactly one abstract method (start()), which makes it a functional interface, so it can be implemented inline with a lambda expression instead of a full class. honk() is a default method: every implementer gets it for free without writing any code. createDefault() is a static factory method that lives on the interface itself, callable as Vehicle.createDefault() without any instance.
Example 3: Implementing multiple interfaces and the diamond problem
public class Main {
public static void main(String[] args) {
Duck duck = new Duck();
duck.fly();
duck.swim();
duck.move();
}
}
interface Flyable {
void fly();
default void move() { System.out.println("Moving by flying"); }
}
interface Swimmable {
void swim();
default void move() { System.out.println("Moving by swimming"); }
}
class Duck implements Flyable, Swimmable {
public void fly() { System.out.println("Duck flies short distances"); }
public void swim() { System.out.println("Duck swims gracefully"); }
@Override
public void move() {
Flyable.super.move();
Swimmable.super.move();
}
}
Output:
Duck flies short distances
Duck swims gracefully
Moving by flying
Moving by swimming
Duck implements both Flyable and Swimmable, each of which defines a default move() method. This is the classic “diamond problem”: if Duck did nothing, the compiler would not know which move() to inherit, so Java forces the implementing class to override the conflicting method itself. Inside the override, Flyable.super.move() and Swimmable.super.move() let you explicitly call each parent interface’s version if you want both behaviors.
Under the Hood
When you call a method through an interface reference, the JVM performs an invokeinterface bytecode instruction (as opposed to invokevirtual for ordinary class method calls). This instruction searches the actual runtime object’s class hierarchy for the correct method implementation, which is slightly more work for the JVM than a direct virtual call — though modern JIT compilers optimize this heavily, so in practice the performance difference is negligible for almost all applications. Default methods are compiled directly into the interface’s .class file with real bytecode bodies, and are inherited by implementing classes exactly like inherited class methods, unless the class explicitly overrides them. Static interface methods, by contrast, are never inherited — they belong solely to the interface type and must be called through its name.
Common Mistakes
Mistake 1: Forgetting to implement all abstract methods. If a class implements an interface but leaves out a required method, it will not compile:
interface Animal {
void eat();
void sleep();
}
class Dog implements Animal {
public void eat() {
System.out.println("Dog is eating");
}
// missing sleep() -- this class will not compile
}
The fix is simple: implement every abstract method the interface declares, or mark the class itself abstract so a later subclass can finish the job.
Mistake 2: Two interfaces with clashing default methods and no override. If a class implements two interfaces that each supply a default method with the same signature, and the class does not override it, the compiler reports an error rather than silently picking one:
interface A {
default void greet() { System.out.println("Hello from A"); }
}
interface B {
default void greet() { System.out.println("Hello from B"); }
}
class C implements A, B {
// compile error: class C inherits unrelated defaults for greet() from types A and B
}
You must explicitly override greet() in C, as shown in Example 3, choosing one implementation, combining both, or writing entirely new logic.
Best Practices
- Name interfaces after a capability or role when possible (
Comparable,Runnable,Flyable) rather than a noun that duplicates a class name. - Keep interfaces small and focused on one responsibility; it’s easier for classes to implement several small interfaces than one bloated one (the Interface Segregation Principle).
- Use default methods sparingly — mainly for adding new methods to an existing interface without breaking every implementer, not as a substitute for a normal base class.
- Program to an interface type in method signatures and variable declarations (
List<String> list = new ArrayList<>();) so implementations can be swapped without touching calling code. - Remember interface fields are always constants (
public static final); don’t rely on interfaces to hold mutable shared state. - When two interfaces provide conflicting default methods, resolve the conflict explicitly with
InterfaceName.super.method()rather than deleting one of the interfaces.
Practice Exercises
Exercise 1: Create an interface Payable with a single abstract method double getPayment(). Implement it in two classes, Employee and Freelancer, each computing payment differently, then print both payments from an array of Payable.
Exercise 2: Write an interface Greetable with one abstract method String name() and a default method greet() that prints "Hello, " + name(). Implement it with a lambda and confirm the default method still works when called on the lambda instance.
Exercise 3: Create two interfaces, Walker and Runner, each with a default method move() that prints a different message. Write a class Athlete that implements both and resolves the conflict by calling both parent default methods inside its own override, similar to the Duck example.
Summary
- An interface declares a contract of methods a class must implement, without dictating how.
- Implementing classes use
implementsand must supply bodies for every abstract method, or be declaredabstractthemselves. - Since Java 8, interfaces may include
defaultmethods (inherited, overridable bodies) andstaticmethods (called on the interface itself). - A class can implement multiple interfaces, giving Java a safe form of multiple inheritance for behavior.
- Calls through an interface reference are dispatched at runtime to the object’s actual class via
invokeinterface. - When two interfaces supply conflicting default methods, the implementing class must override the method and can call each parent’s version with
InterfaceName.super.method(). - Interface fields are always
public static finalconstants; interfaces cannot hold instance state.
