Default Method Conflicts

Introduction

Conflicts occur when a class implements multiple interfaces with the same default method signature.


The Problem

interface Interface1 {
    default void display() {
        System.out.println("Interface1");
    }
}

interface Interface2 {
    default void display() {
        System.out.println("Interface2");
    }
}

// ✗ Conflict! Which display() to use?
class MyClass implements Interface1, Interface2 {
    // Compilation error without resolution
}

Error: Inherits unrelated defaults for display() from Interface1 and Interface2


Resolution Rule

Must override the conflicting method in implementing class.

interface Interface1 {
    default void display() {
        System.out.println("Interface1");
    }
}

interface Interface2 {
    default void display() {
        System.out.println("Interface2");
    }
}

class MyClass implements Interface1, Interface2 {
    // ✓ Resolve by overriding
    @Override
    public void display() {
        System.out.println("MyClass");
    }
}

public class Main {
    public static void main(String[] args) {
        MyClass obj = new MyClass();
        obj.display();  // MyClass
    }
}

Calling Specific Interface Method

Use InterfaceName.super.methodName() syntax.

interface Interface1 {
    default void display() {
        System.out.println("Interface1");
    }
}

interface Interface2 {
    default void display() {
        System.out.println("Interface2");
    }
}

class MyClass implements Interface1, Interface2 {
    @Override
    public void display() {
        // Call Interface1's version
        Interface1.super.display();

        // Call Interface2's version
        Interface2.super.display();

        // Own logic
        System.out.println("MyClass");
    }
}

public class Main {
    public static void main(String[] args) {
        MyClass obj = new MyClass();
        obj.display();
        // Output:
        // Interface1
        // Interface2
        // MyClass
    }
}

Choosing One Interface

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

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

class MyLogger implements Logger, Printer {
    @Override
    public void log(String message) {
        // Choose Logger's implementation
        Logger.super.log(message);
    }
}

public class Main {
    public static void main(String[] args) {
        MyLogger logger = new MyLogger();
        logger.log("Test");  // [LOG] Test
    }
}

Resolution Rules Summary

Priority Order:

  1. Class wins - class method overrides interface default
  2. Sub-interface wins - more specific interface overrides parent
  3. Manual resolution - if conflict, must override explicitly

Rule 1: Class Wins

Class method takes priority over interface default method.

interface MyInterface {
    default void display() {
        System.out.println("Interface");
    }
}

class Parent {
    public void display() {
        System.out.println("Parent");
    }
}

class Child extends Parent implements MyInterface {
    // No conflict - Parent's method wins
}

public class Main {
    public static void main(String[] args) {
        Child obj = new Child();
        obj.display();  // Parent (class wins)
    }
}

Rule 2: Sub-interface Wins

More specific interface overrides parent interface.

interface Parent {
    default void display() {
        System.out.println("Parent Interface");
    }
}

interface Child extends Parent {
    @Override
    default void display() {
        System.out.println("Child Interface");
    }
}

class MyClass implements Child {
    // No conflict - Child interface wins
}

public class Main {
    public static void main(String[] args) {
        MyClass obj = new MyClass();
        obj.display();  // Child Interface
    }
}

Rule 3: Manual Resolution

Unrelated interfaces with same method - must resolve manually.

interface A {
    default void show() {
        System.out.println("A");
    }
}

interface B {
    default void show() {
        System.out.println("B");
    }
}

class MyClass implements A, B {
    // Must override to resolve conflict
    @Override
    public void show() {
        A.super.show();  // Or B.super.show() or custom
    }
}

Complete Example: Multiple Conflicts

interface Drawable {
    default void draw() {
        System.out.println("Drawing");
    }

    default void display() {
        System.out.println("Drawable display");
    }
}

interface Printable {
    default void print() {
        System.out.println("Printing");
    }

    default void display() {
        System.out.println("Printable display");
    }
}

class Document implements Drawable, Printable {
    // Must resolve display() conflict
    @Override
    public void display() {
        System.out.println("Document display");

        // Can call both if needed
        System.out.println("From Drawable:");
        Drawable.super.display();

        System.out.println("From Printable:");
        Printable.super.display();
    }

    // draw() and print() work fine (no conflict)
}

public class Main {
    public static void main(String[] args) {
        Document doc = new Document();
        doc.draw();     // Drawing
        doc.print();    // Printing
        doc.display();  // Custom resolution
    }
}

Diamond Problem

interface A {
    default void show() {
        System.out.println("A");
    }
}

interface B extends A { }

interface C extends A { }

// Diamond: A -> B, A -> C, MyClass implements B,C
class MyClass implements B, C {
    // No conflict - both inherit same method from A
    // No need to override
}

public class Main {
    public static void main(String[] args) {
        MyClass obj = new MyClass();
        obj.show();  // A (no ambiguity)
    }
}

Diamond with Override

interface A {
    default void show() {
        System.out.println("A");
    }
}

interface B extends A {
    @Override
    default void show() {
        System.out.println("B");
    }
}

interface C extends A {
    @Override
    default void show() {
        System.out.println("C");
    }
}

// Conflict! B and C both override A.show() differently
class MyClass implements B, C {
    // Must resolve
    @Override
    public void show() {
        B.super.show();  // Or C.super.show() or custom
    }
}

public class Main {
    public static void main(String[] args) {
        MyClass obj = new MyClass();
        obj.show();  // B
    }
}

Real-World Example: Logging System

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

    default void logError(String error) {
        log("ERROR: " + error);
    }
}

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

    default void logWarning(String warning) {
        log("WARNING: " + warning);
    }
}

class HybridLogger implements FileLogger, DatabaseLogger {
    // Resolve log() conflict
    @Override
    public void log(String message) {
        // Log to both
        FileLogger.super.log(message);
        DatabaseLogger.super.log(message);
    }

    // logError() and logWarning() work fine
}

public class Main {
    public static void main(String[] args) {
        HybridLogger logger = new HybridLogger();

        logger.log("User login");
        // [FILE] User login
        // [DATABASE] User login

        logger.logError("Connection failed");
        // [FILE] ERROR: Connection failed
        // [DATABASE] ERROR: Connection failed

        logger.logWarning("Low memory");
        // [FILE] WARNING: Low memory
        // [DATABASE] WARNING: Low memory
    }
}

Multiple Inheritance Scenario

interface Movable {
    default void move() {
        System.out.println("Moving");
    }
}

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

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

class Duck implements Movable, Flyable, Swimmable {
    private String currentMode = "walking";

    void setMode(String mode) {
        this.currentMode = mode;
    }

    @Override
    public void move() {
        switch (currentMode) {
            case "walking":
                Movable.super.move();
                break;
            case "flying":
                Flyable.super.move();
                break;
            case "swimming":
                Swimmable.super.move();
                break;
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Duck duck = new Duck();

        duck.setMode("walking");
        duck.move();  // Moving

        duck.setMode("flying");
        duck.move();  // Flying

        duck.setMode("swimming");
        duck.move();  // Swimming
    }
}

Abstract Method Wins

Abstract method forces implementation, overrides default.

interface A {
    default void display() {
        System.out.println("Default A");
    }
}

interface B {
    void display();  // Abstract
}

class MyClass implements A, B {
    // Must implement because B has abstract display()
    @Override
    public void display() {
        System.out.println("MyClass implementation");
        // Can still call A's default if needed
        // A.super.display();
    }
}

public class Main {
    public static void main(String[] args) {
        MyClass obj = new MyClass();
        obj.display();  // MyClass implementation
    }
}

Best Practices

  1. Document conflicts in interface JavaDoc
  2. Avoid same method names in unrelated interfaces
  3. Use explicit resolution for clarity
  4. Consider composition over multiple inheritance
  5. Test conflict scenarios thoroughly

Resolution Strategies

Strategy 1: Choose One

class MyClass implements A, B {
    public void method() {
        A.super.method();  // Use A's version
    }
}

Strategy 2: Call Both

class MyClass implements A, B {
    public void method() {
        A.super.method();
        B.super.method();
    }
}

Strategy 3: Custom Logic

class MyClass implements A, B {
    public void method() {
        // Custom implementation
        System.out.println("Custom");
    }
}

Strategy 4: Combine

class MyClass implements A, B {
    public void method() {
        A.super.method();
        System.out.println("Additional logic");
        B.super.method();
    }
}

Quick Reference

// Conflict scenario
interface I1 {
    default void method() { }
}

interface I2 {
    default void method() { }
}

// Must resolve
class MyClass implements I1, I2 {
    @Override
    public void method() {
        // Choose one
        I1.super.method();

        // Or both
        I1.super.method();
        I2.super.method();

        // Or custom
        System.out.println("Custom");
    }
}

// Priority:
// 1. Class method wins over interface default
// 2. Sub-interface wins over parent interface
// 3. Otherwise, must resolve manually

Common Scenarios

ScenarioResolution
Two unrelated interfacesMust override
Sub-interface extends parentSub-interface wins
Class extends & implementsClass wins
Diamond (same default)No conflict
Diamond (different defaults)Must override
Abstract + defaultMust implement

Exam Tips

Remember:

  1. Conflict when same method in multiple interfaces
  2. Must override to resolve
  3. Syntax: Interface.super.method()
  4. Priority: Class > Sub-interface > Manual
  5. Abstract wins over default
  6. Diamond with same method = no conflict
  7. Diamond with different methods = conflict
  8. Can call multiple interface versions
  9. Custom logic allowed
  10. @Override annotation required

Common Questions:

  • When does conflict occur?
  • How to resolve conflict?
  • What is Interface.super syntax?
  • Priority rules for conflicts?
  • Class method vs default method?
  • Sub-interface vs parent interface?
  • Diamond problem in interfaces?
  • Abstract vs default in conflict?
  • Can we call both interface methods?
  • Is override mandatory for conflicts?