Interfaces in Java
1. What is an Interface?
An interface in Java is a reference type that defines a contract — a set of method signatures that any implementing class must fulfil. Think of it as an agreement: "Any class that says it implements this interface promises to provide these behaviours."
Key rules for interfaces (before Java 8):
-
All methods are implicitly
public abstract -
All fields are implicitly
public static final(constants) - Interfaces cannot be instantiated directly
- A class can implement multiple interfaces
- An interface can extend multiple interfaces
Java 8+ added
default and
static methods to interfaces.
Java 9+ added
private methods. The core contract
concept remains unchanged.
2. Defining and Implementing an Interface
Use the interface keyword to define
and implements to use it in a class.
The implementing class must provide a body for every abstract
method in the interface, or itself be declared
abstract.
// Define the interface
public interface Drawable {
// public abstract — implicit; no need to write them
void draw();
void resize(double factor);
// Default method (Java 8+) — optional to override
default void printInfo() {
System.out.println("This is a Drawable object.");
}
}
// Implement the interface in a class
public class Circle implements Drawable {
double radius;
String color;
public Circle(double radius, String color) {
this.radius = radius;
this.color = color;
}
@Override
public void draw() {
System.out.println("Drawing a " + color + " circle with radius " + radius);
}
@Override
public void resize(double factor) {
radius *= factor;
System.out.println("Circle resized. New radius: " + radius);
}
}
public class Square implements Drawable {
double side;
public Square(double side) {
this.side = side;
}
@Override
public void draw() {
System.out.println("Drawing a square with side " + side);
}
@Override
public void resize(double factor) {
side *= factor;
System.out.println("Square resized. New side: " + side);
}
@Override
public void printInfo() { // optionally override default method
System.out.println("Square: side = " + side);
}
}
public class Main {
public static void main(String[] args) {
Drawable c = new Circle(5.0, "red"); // interface reference
Drawable s = new Square(4.0);
c.draw(); // Drawing a red circle with radius 5.0
c.resize(2.0); // Circle resized. New radius: 10.0
c.printInfo(); // This is a Drawable object.
s.draw(); // Drawing a square with side 4.0
s.printInfo(); // Square: side = 4.0 (overridden)
}
}
Circle resized. New radius: 10.0
This is a Drawable object.
Drawing a square with side 4.0
Square: side = 4.0
3. Interface vs Abstract Class
| Feature | Interface | Abstract Class |
|---|---|---|
| Keyword |
interface /
implements
|
abstract class /
extends
|
| Instantiation | Cannot be instantiated | Cannot be instantiated |
| Constructors | No | Yes |
| Instance fields | No (only public static final) |
Yes (any access modifier) |
| Method types | abstract, default, static (Java 8+) | abstract + concrete |
| Multiple inheritance | A class can implement many interfaces | A class can extend only one |
| Access modifiers on methods | Always public (implicit) |
Any access modifier |
| Speed | Slightly slower (extra lookup) | Slightly faster |
| Best used when | Defining capabilities/contracts across unrelated classes | Sharing common code & state among related classes |
Rule of thumb: Prefer interfaces when you want
to define a capability (e.g.,
Comparable,
Serializable). Use abstract classes
when you want to share code and state among closely
related classes.
4. Multiple Interface Implementation
A Java class can implement multiple interfaces simultaneously, separated by commas. This is Java's approach to achieving multiple inheritance of type without the complexity of multiple class inheritance.
public interface Flyable {
void fly();
default String getMode() { return "flying"; }
}
public interface Swimmable {
void swim();
default String getMode() { return "swimming"; }
}
public interface Walkable {
void walk();
}
// Duck implements all three interfaces
public class Duck implements Flyable, Swimmable, Walkable {
private String name;
public Duck(String name) { this.name = name; }
@Override
public void fly() {
System.out.println(name + " is flying!");
}
@Override
public void swim() {
System.out.println(name + " is swimming!");
}
@Override
public void walk() {
System.out.println(name + " is walking!");
}
// MUST override getMode() — both Flyable and Swimmable define it
// (this resolves the diamond problem for default methods)
@Override
public String getMode() {
return Flyable.super.getMode() + " and " + Swimmable.super.getMode();
}
}
public class Main {
public static void main(String[] args) {
Duck duck = new Duck("Donald");
duck.fly();
duck.swim();
duck.walk();
System.out.println("Duck modes: " + duck.getMode());
// Polymorphic references
Flyable f = duck; f.fly();
Swimmable s = duck; s.swim();
}
}
Donald is swimming!
Donald is walking!
Duck modes: flying and swimming
Donald is flying!
Donald is swimming!
Diamond Problem: When two interfaces define the
same default method, the
implementing class must override it to resolve the
conflict. Use
InterfaceName.super.methodName() to
call a specific interface's default implementation.
5. Default Methods (Java 8+)
Java 8 introduced default methods — interface
methods with a body, declared with the
default keyword. They allow adding new
methods to existing interfaces
without breaking all implementing classes.
public interface Logger {
// Abstract method — must be implemented
void log(String message);
// Default method — implementation provided, can be overridden
default void logInfo(String message) {
log("[INFO] " + message);
}
default void logError(String message) {
log("[ERROR] " + message);
}
default void logWarning(String message) {
log("[WARN] " + message);
}
}
// Simple console logger — only needs to implement log()
public class ConsoleLogger implements Logger {
@Override
public void log(String message) {
System.out.println(message);
}
// inherits logInfo, logError, logWarning for free
}
// File logger — overrides one default method
public class FileLogger implements Logger {
private String filename;
public FileLogger(String filename) { this.filename = filename; }
@Override
public void log(String message) {
// In real code: write to file. Here we simulate:
System.out.println("[FILE:" + filename + "] " + message);
}
@Override
public void logError(String message) {
// Custom error handling — also alert
log("[ERROR] " + message);
System.out.println("*** ALERT: Error written to " + filename + " ***");
}
}
public class Main {
public static void main(String[] args) {
Logger console = new ConsoleLogger();
console.logInfo("Application started");
console.logWarning("Low memory");
console.logError("Null pointer detected");
System.out.println("---");
Logger file = new FileLogger("app.log");
file.logInfo("Application started");
file.logError("Disk full"); // uses overridden version
}
}
[WARN] Low memory
[ERROR] Null pointer detected
---
[FILE:app.log] [INFO] Application started
[FILE:app.log] [ERROR] Disk full
*** ALERT: Error written to app.log ***
Why default methods were added: Java 8 needed
to add new methods to
java.util.Collection (like
forEach, stream) without breaking the
thousands of existing classes that implement it.
6. Static Methods in Interfaces (Java 8+)
Interfaces can also define static methods — utility or factory methods related to the interface. They belong to the interface itself and are not inherited by implementing classes. You call them via the interface name.
public interface MathUtils {
// Abstract
double calculate(double a, double b);
// Static utility methods — called as MathUtils.methodName()
static double square(double x) {
return x * x;
}
static double cube(double x) {
return x * x * x;
}
static boolean isPrime(int n) {
if (n < 2) return false;
for (int i = 2; i <= Math.sqrt(n); i++) {
if (n % i == 0) return false;
}
return true;
}
static double clamp(double value, double min, double max) {
return Math.max(min, Math.min(max, value));
}
}
public class Adder implements MathUtils {
@Override
public double calculate(double a, double b) { return a + b; }
}
public class Main {
public static void main(String[] args) {
// Static methods called on the interface, not the class
System.out.println(MathUtils.square(5)); // 25.0
System.out.println(MathUtils.cube(3)); // 27.0
System.out.println(MathUtils.isPrime(17)); // true
System.out.println(MathUtils.isPrime(10)); // false
System.out.println(MathUtils.clamp(150, 0, 100)); // 100.0
// Instance method still works
Adder adder = new Adder();
System.out.println(adder.calculate(3, 4)); // 7.0
// adder.square(5); // COMPILE ERROR — static methods not inherited
}
}
27.0
true
false
100.0
7.0
7. Functional Interface
A functional interface is an interface with
exactly one abstract method. It can have any number of
default or static methods. Annotate it with
@FunctionalInterface to let the
compiler enforce the one-abstract-method rule. Functional
interfaces are the foundation of
lambda expressions in Java 8+.
@FunctionalInterface
public interface Transformer {
String transform(String input); // the single abstract method
// default and static methods allowed
default Transformer andThen(Transformer after) {
return input -> after.transform(this.transform(input));
}
static Transformer identity() {
return input -> input;
}
}
public class Main {
public static void main(String[] args) {
// Lambda — short form for implementing the single abstract method
Transformer upper = s -> s.toUpperCase();
Transformer trim = s -> s.trim();
Transformer exclaim = s -> s + "!";
System.out.println(upper.transform(" hello ")); // " HELLO "
System.out.println(trim.transform(" hello ")); // "hello"
// Chaining with andThen (default method)
Transformer combined = trim.andThen(upper).andThen(exclaim);
System.out.println(combined.transform(" java ")); // "JAVA!"
// Anonymous class — the old way before lambdas
Transformer reverse = new Transformer() {
@Override
public String transform(String input) {
return new StringBuilder(input).reverse().toString();
}
};
System.out.println(reverse.transform("Hello")); // "olleH"
// Built-in functional interfaces from java.util.function
java.util.function.Predicate<Integer> isEven = n -> n % 2 == 0;
java.util.function.Function<Integer, String> intToStr = n -> "Value: " + n;
System.out.println(isEven.test(4)); // true
System.out.println(intToStr.apply(42)); // Value: 42
}
}
hello
JAVA!
olleH
true
Value: 42
Java's java.util.function package
ships with many ready-made functional interfaces:
Predicate<T>,
Function<T,R>,
Consumer<T>,
Supplier<T>,
BiFunction<T,U,R>, and more.
8. Nested Interface
An interface declared
inside a class or another interface is called a
nested interface. It is implicitly
public static when inside another
interface, and can have any access modifier when inside a class.
// Nested interface inside a class
public class DataProcessor {
// Nested interface — accessed as DataProcessor.Validator
public interface Validator<T> {
boolean validate(T value);
default boolean isInvalid(T value) { return !validate(value); }
}
private Validator<String> validator;
public DataProcessor(Validator<String> validator) {
this.validator = validator;
}
public void process(String data) {
if (validator.validate(data)) {
System.out.println("Processing: " + data);
} else {
System.out.println("Rejected: '" + data + "' failed validation.");
}
}
}
// Interface nesting inside another interface
public interface OuterInterface {
void outerMethod();
interface InnerInterface { // implicitly public static
void innerMethod();
}
}
// Implementing nested interface
class ConcreteOuter implements OuterInterface, OuterInterface.InnerInterface {
@Override public void outerMethod() { System.out.println("Outer method"); }
@Override public void innerMethod() { System.out.println("Inner method"); }
}
public class Main {
public static void main(String[] args) {
// Use lambda to implement nested Validator
DataProcessor.Validator<String> nonEmpty = s -> s != null && !s.trim().isEmpty();
DataProcessor dp = new DataProcessor(nonEmpty);
dp.process("Hello World"); // Processing: Hello World
dp.process(""); // Rejected: '' failed validation.
dp.process(" "); // Rejected: ' ' failed validation.
ConcreteOuter co = new ConcreteOuter();
co.outerMethod(); // Outer method
co.innerMethod(); // Inner method
}
}
Rejected: '' failed validation.
Rejected: ' ' failed validation.
Outer method
Inner method
9. Marker Interface
Marker interfaces add metadata or capabilities to a class. Continue with the complete beginner-friendly guide covering Serializable, deserialization, serialVersionUID, Externalizable, and Cloneable.
Open Marker Interface Tutorial →10. Interface with Constants
All fields declared in an interface are implicitly
public static final, making them
constants. While this is a valid pattern, prefer using
enum
or dedicated constant classes in modern Java.
public interface AppConfig {
// All fields are implicitly public static final
String APP_NAME = "MyJavaApp";
String VERSION = "2.1.0";
int MAX_RETRIES = 3;
double TIMEOUT_SEC = 30.0;
// Nested constants grouped by category
interface HttpStatus {
int OK = 200;
int CREATED = 201;
int BAD_REQUEST = 400;
int UNAUTHORIZED = 401;
int NOT_FOUND = 404;
int INTERNAL_SERVER_ERROR = 500;
}
interface DatabaseConfig {
String HOST = "localhost";
int PORT = 5432;
String DB_NAME = "mydb";
}
// Methods can also be declared
void initialize();
}
public class Application implements AppConfig {
@Override
public void initialize() {
System.out.println("Starting " + APP_NAME + " v" + VERSION);
System.out.println("Max retries: " + MAX_RETRIES);
System.out.println("Timeout: " + TIMEOUT_SEC + "s");
}
public void connect() {
System.out.println("Connecting to " + DatabaseConfig.HOST
+ ":" + DatabaseConfig.PORT + "/" + DatabaseConfig.DB_NAME);
}
}
public class Main {
public static void main(String[] args) {
Application app = new Application();
app.initialize();
app.connect();
// Access constants directly via interface name
System.out.println("HTTP OK: " + AppConfig.HttpStatus.OK);
System.out.println("HTTP Not Found: " + AppConfig.HttpStatus.NOT_FOUND);
// Implementing class also gets direct access
System.out.println("App timeout: " + app.TIMEOUT_SEC + "s");
}
}
Max retries: 3
Timeout: 30.0s
Connecting to localhost:5432/mydb
HTTP OK: 200
HTTP Not Found: 404
App timeout: 30.0s
Constant Interface Anti-Pattern: Using
interfaces solely to hold constants (the "Constant Interface"
pattern) is considered bad practice by Josh Bloch in
Effective Java. Prefer
enum types or static utility classes
with private constructors for
constants.
