You want to add a new method to an interface used by 100 classes. Without default methods, all 100 must be updated. Default methods let you add new methods with implementations - existing code keeps working.

Basic default method

Provide implementation in an interface.

BasicDefault.java
// Basic Default Method Syntax

interface Greeting {
    // Abstract method - MUST be implemented
    String getName();

    // Default method - has implementation
    default void sayHello() {
        System.out.println("Hello, " + getName() + "!");
    }

    default void sayGoodbye() {
        System.out.println("Goodbye, " + getName() + "!");
    }
}

// Implementing class only needs to implement abstract methods
class Person implements Greeting {
    private String name;

    Person(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return name;
    }
    // sayHello() and sayGoodbye() inherited from interface!
}

public class BasicDefault {
    public static void main(String[] args) {
        System.out.println("=== Basic Default Methods ===\n");

        String personName = ;
        Person alice = new Person(personName);

        // Calling abstract method (implemented)
        System.out.println("Name: " + alice.getName());

        // Calling default methods (inherited)
        alice.sayHello();
        alice.sayGoodbye();

        System.out.println("\n=== Another Implementation ===");

        // Anonymous class also gets defaults
        String robotName = ;
        Greeting robot = new Greeting() {
            @Override
            public String getName() {
                return robotName;
            }
        };

        robot.sayHello();  // Uses default

        System.out.println("\n=== Why Default Methods? ===");
        System.out.println("""
            Before Java 8:
            - Adding method to interface = breaking change
            - All implementations had to be updated

            With Default Methods:
            - Can add methods with implementation
            - Existing code continues to work
            - Interface evolution without breaking changes
            """);
    }
}
// Basic Default Method Syntax

interface Greeting {
    // Abstract method - MUST be implemented
    String getName();

    // Default method - has implementation
    default void sayHello() {
        System.out.println("Hello, " + getName() + "!");
    }

    default void sayGoodbye() {
        System.out.println("Goodbye, " + getName() + "!");
    }
}

// Implementing class only needs to implement abstract methods
class Person implements Greeting {
    private String name;

    Person(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return name;
    }
    // sayHello() and sayGoodbye() inherited from interface!
}

public class BasicDefault {
    public static void main(String[] args) {
        System.out.println("=== Basic Default Methods ===\n");

        String personName = ;
        Person alice = new Person(personName);

        // Calling abstract method (implemented)
        System.out.println("Name: " + alice.getName());

        // Calling default methods (inherited)
        alice.sayHello();
        alice.sayGoodbye();

        System.out.println("\n=== Another Implementation ===");

        // Anonymous class also gets defaults
        String robotName = ;
        Greeting robot = new Greeting() {
            @Override
            public String getName() {
                return robotName;
            }
        };

        robot.sayHello();  // Uses default

        System.out.println("\n=== Why Default Methods? ===");
        System.out.println("""
            Before Java 8:
            - Adding method to interface = breaking change
            - All implementations had to be updated

            With Default Methods:
            - Can add methods with implementation
            - Existing code continues to work
            - Interface evolution without breaking changes
            """);
    }
}
// Basic Default Method Syntax

interface Greeting {
    // Abstract method - MUST be implemented
    String getName();

    // Default method - has implementation
    default void sayHello() {
        System.out.println("Hello, " + getName() + "!");
    }

    default void sayGoodbye() {
        System.out.println("Goodbye, " + getName() + "!");
    }
}

// Implementing class only needs to implement abstract methods
class Person implements Greeting {
    private String name;

    Person(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return name;
    }
    // sayHello() and sayGoodbye() inherited from interface!
}

public class BasicDefault {
    public static void main(String[] args) {
        System.out.println("=== Basic Default Methods ===\n");

        String personName = ;
        Person alice = new Person(personName);

        // Calling abstract method (implemented)
        System.out.println("Name: " + alice.getName());

        // Calling default methods (inherited)
        alice.sayHello();
        alice.sayGoodbye();

        System.out.println("\n=== Another Implementation ===");

        // Anonymous class also gets defaults
        String robotName = ;
        Greeting robot = new Greeting() {
            @Override
            public String getName() {
                return robotName;
            }
        };

        robot.sayHello();  // Uses default

        System.out.println("\n=== Why Default Methods? ===");
        System.out.println("""
            Before Java 8:
            - Adding method to interface = breaking change
            - All implementations had to be updated

            With Default Methods:
            - Can add methods with implementation
            - Existing code continues to work
            - Interface evolution without breaking changes
            """);
    }
}
// Basic Default Method Syntax

interface Greeting {
    // Abstract method - MUST be implemented
    String getName();

    // Default method - has implementation
    default void sayHello() {
        System.out.println("Hello, " + getName() + "!");
    }

    default void sayGoodbye() {
        System.out.println("Goodbye, " + getName() + "!");
    }
}

// Implementing class only needs to implement abstract methods
class Person implements Greeting {
    private String name;

    Person(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return name;
    }
    // sayHello() and sayGoodbye() inherited from interface!
}

public class BasicDefault {
    public static void main(String[] args) {
        System.out.println("=== Basic Default Methods ===\n");

        String personName = ;
        Person alice = new Person(personName);

        // Calling abstract method (implemented)
        System.out.println("Name: " + alice.getName());

        // Calling default methods (inherited)
        alice.sayHello();
        alice.sayGoodbye();

        System.out.println("\n=== Another Implementation ===");

        // Anonymous class also gets defaults
        String robotName = ;
        Greeting robot = new Greeting() {
            @Override
            public String getName() {
                return robotName;
            }
        };

        robot.sayHello();  // Uses default

        System.out.println("\n=== Why Default Methods? ===");
        System.out.println("""
            Before Java 8:
            - Adding method to interface = breaking change
            - All implementations had to be updated

            With Default Methods:
            - Can add methods with implementation
            - Existing code continues to work
            - Interface evolution without breaking changes
            """);
    }
}
// Basic Default Method Syntax

interface Greeting {
    // Abstract method - MUST be implemented
    String getName();

    // Default method - has implementation
    default void sayHello() {
        System.out.println("Hello, " + getName() + "!");
    }

    default void sayGoodbye() {
        System.out.println("Goodbye, " + getName() + "!");
    }
}

// Implementing class only needs to implement abstract methods
class Person implements Greeting {
    private String name;

    Person(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return name;
    }
    // sayHello() and sayGoodbye() inherited from interface!
}

public class BasicDefault {
    public static void main(String[] args) {
        System.out.println("=== Basic Default Methods ===\n");

        String personName = ;
        Person alice = new Person(personName);

        // Calling abstract method (implemented)
        System.out.println("Name: " + alice.getName());

        // Calling default methods (inherited)
        alice.sayHello();
        alice.sayGoodbye();

        System.out.println("\n=== Another Implementation ===");

        // Anonymous class also gets defaults
        String robotName = ;
        Greeting robot = new Greeting() {
            @Override
            public String getName() {
                return robotName;
            }
        };

        robot.sayHello();  // Uses default

        System.out.println("\n=== Why Default Methods? ===");
        System.out.println("""
            Before Java 8:
            - Adding method to interface = breaking change
            - All implementations had to be updated

            With Default Methods:
            - Can add methods with implementation
            - Existing code continues to work
            - Interface evolution without breaking changes
            """);
    }
}
// Basic Default Method Syntax

interface Greeting {
    // Abstract method - MUST be implemented
    String getName();

    // Default method - has implementation
    default void sayHello() {
        System.out.println("Hello, " + getName() + "!");
    }

    default void sayGoodbye() {
        System.out.println("Goodbye, " + getName() + "!");
    }
}

// Implementing class only needs to implement abstract methods
class Person implements Greeting {
    private String name;

    Person(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return name;
    }
    // sayHello() and sayGoodbye() inherited from interface!
}

public class BasicDefault {
    public static void main(String[] args) {
        System.out.println("=== Basic Default Methods ===\n");

        String personName = ;
        Person alice = new Person(personName);

        // Calling abstract method (implemented)
        System.out.println("Name: " + alice.getName());

        // Calling default methods (inherited)
        alice.sayHello();
        alice.sayGoodbye();

        System.out.println("\n=== Another Implementation ===");

        // Anonymous class also gets defaults
        String robotName = ;
        Greeting robot = new Greeting() {
            @Override
            public String getName() {
                return robotName;
            }
        };

        robot.sayHello();  // Uses default

        System.out.println("\n=== Why Default Methods? ===");
        System.out.println("""
            Before Java 8:
            - Adding method to interface = breaking change
            - All implementations had to be updated

            With Default Methods:
            - Can add methods with implementation
            - Existing code continues to work
            - Interface evolution without breaking changes
            """);
    }
}
// Basic Default Method Syntax

interface Greeting {
    // Abstract method - MUST be implemented
    String getName();

    // Default method - has implementation
    default void sayHello() {
        System.out.println("Hello, " + getName() + "!");
    }

    default void sayGoodbye() {
        System.out.println("Goodbye, " + getName() + "!");
    }
}

// Implementing class only needs to implement abstract methods
class Person implements Greeting {
    private String name;

    Person(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return name;
    }
    // sayHello() and sayGoodbye() inherited from interface!
}

public class BasicDefault {
    public static void main(String[] args) {
        System.out.println("=== Basic Default Methods ===\n");

        String personName = ;
        Person alice = new Person(personName);

        // Calling abstract method (implemented)
        System.out.println("Name: " + alice.getName());

        // Calling default methods (inherited)
        alice.sayHello();
        alice.sayGoodbye();

        System.out.println("\n=== Another Implementation ===");

        // Anonymous class also gets defaults
        String robotName = ;
        Greeting robot = new Greeting() {
            @Override
            public String getName() {
                return robotName;
            }
        };

        robot.sayHello();  // Uses default

        System.out.println("\n=== Why Default Methods? ===");
        System.out.println("""
            Before Java 8:
            - Adding method to interface = breaking change
            - All implementations had to be updated

            With Default Methods:
            - Can add methods with implementation
            - Existing code continues to work
            - Interface evolution without breaking changes
            """);
    }
}
// Basic Default Method Syntax

interface Greeting {
    // Abstract method - MUST be implemented
    String getName();

    // Default method - has implementation
    default void sayHello() {
        System.out.println("Hello, " + getName() + "!");
    }

    default void sayGoodbye() {
        System.out.println("Goodbye, " + getName() + "!");
    }
}

// Implementing class only needs to implement abstract methods
class Person implements Greeting {
    private String name;

    Person(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return name;
    }
    // sayHello() and sayGoodbye() inherited from interface!
}

public class BasicDefault {
    public static void main(String[] args) {
        System.out.println("=== Basic Default Methods ===\n");

        String personName = ;
        Person alice = new Person(personName);

        // Calling abstract method (implemented)
        System.out.println("Name: " + alice.getName());

        // Calling default methods (inherited)
        alice.sayHello();
        alice.sayGoodbye();

        System.out.println("\n=== Another Implementation ===");

        // Anonymous class also gets defaults
        String robotName = ;
        Greeting robot = new Greeting() {
            @Override
            public String getName() {
                return robotName;
            }
        };

        robot.sayHello();  // Uses default

        System.out.println("\n=== Why Default Methods? ===");
        System.out.println("""
            Before Java 8:
            - Adding method to interface = breaking change
            - All implementations had to be updated

            With Default Methods:
            - Can add methods with implementation
            - Existing code continues to work
            - Interface evolution without breaking changes
            """);
    }
}
// Basic Default Method Syntax

interface Greeting {
    // Abstract method - MUST be implemented
    String getName();

    // Default method - has implementation
    default void sayHello() {
        System.out.println("Hello, " + getName() + "!");
    }

    default void sayGoodbye() {
        System.out.println("Goodbye, " + getName() + "!");
    }
}

// Implementing class only needs to implement abstract methods
class Person implements Greeting {
    private String name;

    Person(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return name;
    }
    // sayHello() and sayGoodbye() inherited from interface!
}

public class BasicDefault {
    public static void main(String[] args) {
        System.out.println("=== Basic Default Methods ===\n");

        String personName = ;
        Person alice = new Person(personName);

        // Calling abstract method (implemented)
        System.out.println("Name: " + alice.getName());

        // Calling default methods (inherited)
        alice.sayHello();
        alice.sayGoodbye();

        System.out.println("\n=== Another Implementation ===");

        // Anonymous class also gets defaults
        String robotName = ;
        Greeting robot = new Greeting() {
            @Override
            public String getName() {
                return robotName;
            }
        };

        robot.sayHello();  // Uses default

        System.out.println("\n=== Why Default Methods? ===");
        System.out.println("""
            Before Java 8:
            - Adding method to interface = breaking change
            - All implementations had to be updated

            With Default Methods:
            - Can add methods with implementation
            - Existing code continues to work
            - Interface evolution without breaking changes
            """);
    }
}

default methods have bodies. Implementing classes inherit the implementation.

default method Interface method with body. Implementing classes get it without writing it.

Override default methods

Implementing classes can override defaults.

OverrideDefault.java
// Overriding Default Methods

interface Logger {
    default void log(String message) {
        System.out.println("[LOG] " + message);
    }

    default void warn(String message) {
        System.out.println("[WARN] " + message);
    }

    default void error(String message) {
        System.out.println("[ERROR] " + message);
    }
}

// Uses all defaults
class BasicLogger implements Logger {
    // No overrides - uses all default implementations
}

// Overrides some defaults
class TimestampLogger implements Logger {
    @Override
    public void log(String message) {
        System.out.println("[" + timestamp() + "] LOG: " + message);
    }

    @Override
    public void error(String message) {
        System.out.println("!!! [" + timestamp() + "] ERROR: " + message + " !!!");
    }

    // warn() still uses default

    private String timestamp() {
        return "09:30:00";
    }
}

// Overrides ALL defaults
class JsonLogger implements Logger {
    @Override
    public void log(String message) {
        System.out.println(format("LOG", message));
    }

    @Override
    public void warn(String message) {
        System.out.println(format("WARN", message));
    }

    @Override
    public void error(String message) {
        System.out.println(format("ERROR", message));
    }

    private String format(String level, String msg) {
        return "{\"level\":\"" + level + "\",\"message\":\"" + msg + "\"}";
    }
}

public class OverrideDefault {
    public static void main(String[] args) {
        System.out.println("=== BasicLogger (all defaults) ===");
        Logger basic = new BasicLogger();
        basic.log("Application started");
        basic.warn("Low memory");
        basic.error("Connection failed");

        System.out.println("\n=== TimestampLogger (partial override) ===");
        Logger timestamp = new TimestampLogger();
        timestamp.log("Application started");
        timestamp.warn("Low memory");  // Uses default!
        timestamp.error("Connection failed");

        System.out.println("\n=== JsonLogger (full override) ===");
        Logger json = new JsonLogger();
        json.log("Application started");
        json.warn("Low memory");
        json.error("Connection failed");

        System.out.println("\n=== Polymorphism with Defaults ===");

        Logger[] loggers = {new BasicLogger(), new TimestampLogger(), new JsonLogger()};

        for (Logger logger : loggers) {
            System.out.println("\n" + logger.getClass().getSimpleName() + ":");
            logger.log("Test message");
        }
    }
}

Classes can accept the default or provide their own implementation.

Multiple interfaces with defaults

Handle conflicts when interfaces have same default method.

MultipleDefaults.java
// Multiple Interfaces with Default Methods

interface Flyable {
    default void move() {
        System.out.println("Flying through the air");
    }

    default void takeOff() {
        System.out.println("Taking off...");
    }
}

interface Swimmable {
    default void move() {
        System.out.println("Swimming through the water");
    }

    default void dive() {
        System.out.println("Diving deep...");
    }
}

// Implements only Flyable - no conflict
class Bird implements Flyable {
    // Gets Flyable.move() and takeOff()
}

// Implements only Swimmable - no conflict
class Fish implements Swimmable {
    // Gets Swimmable.move() and dive()
}

// Implements BOTH - MUST resolve conflict!
class Duck implements Flyable, Swimmable {

    @Override
    public void move() {
        // MUST override - compiler error otherwise!
        System.out.println("Duck can fly and swim!");
    }

    // Can call specific interface's default
    public void flyMove() {
        Flyable.super.move();  // Calls Flyable's default
    }

    public void swimMove() {
        Swimmable.super.move();  // Calls Swimmable's default
    }

    // takeOff() from Flyable - no conflict
    // dive() from Swimmable - no conflict
}

// Another approach: delegate to one interface
class Seaplane implements Flyable, Swimmable {
    @Override
    public void move() {
        // Delegate to Flyable's version
        Flyable.super.move();
    }
}

public class MultipleDefaults {
    public static void main(String[] args) {
        System.out.println("=== Single Interface ===");
        Bird bird = new Bird();
        bird.move();
        bird.takeOff();

        Fish fish = new Fish();
        fish.move();
        fish.dive();

        System.out.println("\n=== Diamond Problem Resolution ===");
        Duck duck = new Duck();
        duck.move();  // Duck's override
        duck.takeOff();  // From Flyable
        duck.dive();  // From Swimmable

        System.out.println("\n=== Calling Specific Interface's Default ===");
        duck.flyMove();
        duck.swimMove();

        System.out.println("\n=== Delegation Approach ===");
        Seaplane plane = new Seaplane();
        plane.move();  // Delegates to Flyable

        System.out.println("\n=== Diamond Problem Rules ===");
        System.out.println("""
            When two interfaces have same default method:
            1. Implementing class MUST override
            2. Compiler error if not resolved
            3. Can call specific default: Interface.super.method()

            No conflict if:
            - Methods have different names
            - One interface extends the other
            """);
    }
}

Class must override if two interfaces have same default method signature.

diamond problem Conflict when class inherits same method from multiple interfaces. Must resolve explicitly.

Default calls abstract

Default methods can use abstract methods.

DefaultWithAbstract.java
// Combining Default and Abstract Methods

interface DataProcessor {
    // Abstract methods - MUST implement
    String getSource();
    void processItem(String item);

    // Default methods - use abstract methods
    default void processAll(String[] items) {
        System.out.println("Processing from: " + getSource());
        for (String item : items) {
            processItem(item);  // Calls abstract method
        }
        System.out.println("Done processing " + items.length + " items");
    }

    default void validate(String item) {
        if (item == null || item.isEmpty()) {
            throw new IllegalArgumentException("Invalid item");
        }
    }
}

// Implementation must provide abstract methods
class FileProcessor implements DataProcessor {
    private String filename;

    FileProcessor(String filename) {
        this.filename = filename;
    }

    @Override
    public String getSource() {
        return "File: " + filename;
    }

    @Override
    public void processItem(String item) {
        validate(item);  // Uses default!
        System.out.println("  Processing: " + item.toUpperCase());
    }
}

class DatabaseProcessor implements DataProcessor {
    private String tableName;

    DatabaseProcessor(String tableName) {
        this.tableName = tableName;
    }

    @Override
    public String getSource() {
        return "Database: " + tableName;
    }

    @Override
    public void processItem(String item) {
        validate(item);
        System.out.println("  INSERT INTO " + tableName + " VALUES ('" + item + "')");
    }
}

// Interface with hook methods
interface Workflow {
    // Template with hooks
    default void execute() {
        beforeStart();  // Hook
        doWork();       // Abstract - must implement
        afterComplete(); // Hook
    }

    // Hooks with empty defaults
    default void beforeStart() {
        // Empty default - override to add behavior
    }

    default void afterComplete() {
        // Empty default - override to add behavior
    }

    // Abstract - must implement
    void doWork();
}

class SimpleTask implements Workflow {
    @Override
    public void doWork() {
        System.out.println("Doing simple work...");
    }
    // Uses empty hooks
}

class LoggedTask implements Workflow {
    @Override
    public void doWork() {
        System.out.println("Doing logged work...");
    }

    @Override
    public void beforeStart() {
        System.out.println("[LOG] Task starting...");
    }

    @Override
    public void afterComplete() {
        System.out.println("[LOG] Task completed!");
    }
}

public class DefaultWithAbstract {
    public static void main(String[] args) {
        System.out.println("=== DataProcessor ===\n");

        String[] data = {"apple", "banana", "cherry"};

        DataProcessor fileProc = new FileProcessor("data.txt");
        fileProc.processAll(data);

        System.out.println();

        DataProcessor dbProc = new DatabaseProcessor("fruits");
        dbProc.processAll(data);

        System.out.println("\n=== Workflow with Hooks ===\n");

        System.out.println("SimpleTask:");
        Workflow simple = new SimpleTask();
        simple.execute();

        System.out.println("\nLoggedTask:");
        Workflow logged = new LoggedTask();
        logged.execute();

        System.out.println("\n=== Pattern Summary ===");
        System.out.println("""
            Template Method in Interfaces:
            - Default method defines algorithm structure
            - Abstract methods are customization points
            - Implementations provide specific behavior

            Hook Pattern:
            - Default methods with empty bodies
            - Override to add optional behavior
            - Clean extension points
            """);
    }
}

Default implementation calls abstract methods that subclasses implement.

API evolution

Add new methods to existing interfaces safely.

RealWorldEvolution.java
// Using Default Methods for API Evolution

// Version 1 of the API (before default methods)
interface PaymentProcessorV1 {
    boolean processPayment(double amount);
    boolean refund(String transactionId);
}

// Old implementation works with V1
class StripePaymentV1 implements PaymentProcessorV1 {
    @Override
    public boolean processPayment(double amount) {
        System.out.println("Stripe: Processing $" + amount);
        return true;
    }

    @Override
    public boolean refund(String transactionId) {
        System.out.println("Stripe: Refunding " + transactionId);
        return true;
    }
}

// Version 2 - Adding new features with defaults
interface PaymentProcessorV2 {
    boolean processPayment(double amount);
    boolean refund(String transactionId);

    // NEW in V2 - won't break existing implementations!
    default boolean processRecurring(double amount, String schedule) {
        System.out.println("Default recurring: $" + amount + " " + schedule);
        return processPayment(amount);  // Fallback to regular payment
    }

    // NEW in V2
    default void validatePaymentMethod() {
        System.out.println("Default validation passed");
    }

    // NEW in V2 - utility method
    default String formatAmount(double amount) {
        return String.format("$%.2f", amount);
    }
}

// Old implementation upgraded to V2 - still works!
class StripePaymentV2 implements PaymentProcessorV2 {
    @Override
    public boolean processPayment(double amount) {
        System.out.println("Stripe: Processing " + formatAmount(amount));
        return true;
    }

    @Override
    public boolean refund(String transactionId) {
        System.out.println("Stripe: Refunding " + transactionId);
        return true;
    }
    // processRecurring() - uses default
    // validatePaymentMethod() - uses default
}

// New implementation can override new methods
class PayPalPaymentV2 implements PaymentProcessorV2 {
    @Override
    public boolean processPayment(double amount) {
        System.out.println("PayPal: Processing " + formatAmount(amount));
        return true;
    }

    @Override
    public boolean refund(String transactionId) {
        System.out.println("PayPal: Refunding " + transactionId);
        return true;
    }

    @Override
    public boolean processRecurring(double amount, String schedule) {
        System.out.println("PayPal Subscriptions: " + formatAmount(amount) +
                          " billed " + schedule);
        return true;
    }

    @Override
    public void validatePaymentMethod() {
        System.out.println("PayPal: Validating linked account...");
    }
}

public class RealWorldEvolution {
    public static void main(String[] args) {
        System.out.println("=== API Evolution Example ===\n");

        System.out.println("--- Stripe (uses defaults) ---");
        PaymentProcessorV2 stripe = new StripePaymentV2();
        stripe.processPayment(99.99);
        stripe.validatePaymentMethod();  // Uses default
        stripe.processRecurring(9.99, "monthly");  // Uses default

        System.out.println("\n--- PayPal (custom implementations) ---");
        PaymentProcessorV2 paypal = new PayPalPaymentV2();
        paypal.processPayment(99.99);
        paypal.validatePaymentMethod();  // Custom
        paypal.processRecurring(9.99, "monthly");  // Custom

        System.out.println("\n=== Benefits of Default Methods ===");
        System.out.println("""
            1. Backward Compatibility
               - Add methods without breaking existing code
               - Old implementations continue to work

            2. Optional Features
               - Provide sensible defaults
               - Override only when needed

            3. Interface Evolution
               - Grow APIs over time
               - No need for adapter classes

            4. Utility Methods
               - Add helper methods to interfaces
               - All implementations can use them
            """);

        System.out.println("=== Common Use Cases ===");
        System.out.println("""
            - Collection.forEach() - added in Java 8
            - Comparator.reversed() - added in Java 8
            - List.sort() - added in Java 8
            - Stream API methods on collections
            """);
    }
}

Collections API added forEach(), stream() via default methods. No breakage.

Exercise: Practical.java

Evolve an interface with new default methods