Java 9 – Private Methods in Interfaces: Complete Guide with Examples
Java 8 allowed interfaces to have default and static methods with implementations. But once you had multiple default methods, you had no clean way to share helper code between them without exposing that helper as a public part of the interface contract. Java 9 solved this by allowing private methods in interfaces.
This tutorial explains why private interface methods were added, how to use them, and the difference between private and private static interface methods.
The Problem Before Java 9
Imagine an interface with two default methods that share some logic. Before Java 9, you had two ugly options:
// Option 1: Duplicate code in both default methods (bad)
interface Validator {
default boolean validateAge(int age) {
if (age < 0 || age > 150) {
System.out.println("[ERROR] Invalid age: " + age);
return false;
}
return true;
}
default boolean validateScore(int score) {
if (score < 0 || score > 100) {
System.out.println("[ERROR] Invalid score: " + score); // DUPLICATED
return false;
}
return true;
}
}
// Option 2: Expose the helper as a default method (exposes internal detail)
interface Validator {
// forced to make this "public" even though it's an internal helper
default void logError(String msg) {
System.out.println("[ERROR] " + msg);
}
// ...
}
The Java 9 Solution: Private Methods
package com.java9r;
/**
* Demonstrates Java 9 private interface methods.
* Private methods share code between default methods without
* exposing internal helpers as part of the public API.
*/
interface Validator {
// Public API – the methods callers use
default boolean validateAge(int age) {
if (age < 0 || age > 150) {
logError("Invalid age: " + age + ". Must be 0-150.");
return false;
}
return true;
}
default boolean validateScore(int score) {
if (score < 0 || score > 100) {
logError("Invalid score: " + score + ". Must be 0-100.");
return false;
}
return true;
}
default boolean validateName(String name) {
if (name == null || name.trim().isEmpty()) {
logError("Name cannot be null or empty.");
return false;
}
if (name.length() < 2 || name.length() > 50) {
logError("Name length must be 2-50 characters. Got: " + name.length());
return false;
}
return true;
}
// Private method – shared by all default methods above
// NOT part of the public interface contract
private void logError(String message) {
System.out.println("[VALIDATION ERROR] " + message);
}
}
Using the Interface
package com.java9r;
/**
* Concrete class implementing the Validator interface.
* Note: logError() is not accessible from StudentValidator.
*/
class StudentValidator implements Validator {
// No need to implement anything – all default methods are inherited
// logError() is private – not visible here
}
public class PrivateInterfaceMethodDemo {
public static void main(String[] args) {
var validator = new StudentValidator();
System.out.println("=== Age Validation ===");
System.out.println(validator.validateAge(25)); // true
System.out.println(validator.validateAge(-5)); // false
System.out.println(validator.validateAge(200)); // false
System.out.println("\n=== Score Validation ===");
System.out.println(validator.validateScore(85)); // true
System.out.println(validator.validateScore(110)); // false
System.out.println(validator.validateScore(-1)); // false
System.out.println("\n=== Name Validation ===");
System.out.println(validator.validateName("Ravi")); // true
System.out.println(validator.validateName("")); // false
System.out.println(validator.validateName(null)); // false
}
}
Expected Output
=== Age Validation ===
true
[VALIDATION ERROR] Invalid age: -5. Must be 0-150.
false
[VALIDATION ERROR] Invalid age: 200. Must be 0-150.
false
=== Score Validation ===
true
[VALIDATION ERROR] Invalid score: 110. Must be 0-100.
false
[VALIDATION ERROR] Invalid score: -1. Must be 0-100.
false
=== Name Validation ===
true
[VALIDATION ERROR] Name cannot be null or empty.
false
[VALIDATION ERROR] Name cannot be null or empty.
false
Private Static Methods in Interfaces
Java 9 also allows private static methods in interfaces. These are used by static methods in the interface (whereas non-static private methods are used by default instance methods):
interface MathUtils {
// Public static method
static double circleArea(double radius) {
validate(radius, "radius"); // calls private static helper
return Math.PI * radius * radius;
}
static double rectangleArea(double width, double height) {
validate(width, "width");
validate(height, "height");
return width * height;
}
// Private static helper – shared by static methods above
private static void validate(double value, String name) {
if (value <= 0) {
throw new IllegalArgumentException(name + " must be positive, got: " + value);
}
}
}
// Usage:
System.out.println(MathUtils.circleArea(5)); // 78.53...
System.out.println(MathUtils.rectangleArea(4, 6)); // 24.0
// MathUtils.circleArea(-1); // throws IllegalArgumentException
Four Types of Interface Methods (Java 9)
| Method Type | Since | Inherited? | Has body? | Purpose |
|---|---|---|---|---|
| abstract | Java 1 | Yes (must implement) | No | Core contract |
| default | Java 8 | Yes (optional override) | Yes | Optional behavior with default impl |
| static | Java 8 | No | Yes | Utility methods on the interface |
| private | Java 9 | No | Yes | Shared helper for default methods |
| private static | Java 9 | No | Yes | Shared helper for static methods |
Rules for Private Interface Methods
- Must have a body (like
defaultandstaticmethods) - Cannot be
abstract - Cannot be
public— they are always private - Non-static private methods can only be called from
defaultmethods in the same interface - Private static methods can be called from both
staticanddefaultmethods in the same interface - Not inherited by implementing classes or sub-interfaces
Real-World Use Case: Logging Interface
interface AppLogger {
default void info(String message) {
log("INFO", message);
}
default void warn(String message) {
log("WARN", message);
}
default void error(String message) {
log("ERROR", message);
}
// Private – formats and prints the log line consistently
private void log(String level, String message) {
var timestamp = java.time.LocalDateTime.now()
.format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.printf("[%s] [%s] %s%n", timestamp, level, message);
}
}
Summary
Java 9's private interface methods solve the code duplication problem that existed when multiple default or static methods needed to share helper logic. Private methods are invisible to implementing classes and sub-interfaces — they are purely an implementation detail. Use private methods to extract repeated logic from default methods, and private static methods to extract shared logic from static utility methods in an interface. This makes interfaces cleaner, avoids code duplication, and keeps internal helpers out of the public API.
Comments