8.3 Method Overriding and Polymorphic Method Execution

Key Takeaways

  • Method overriding occurs when a subclass provides a specific implementation for an instance method already defined in its superclass using the exact same signature (name and parameter types).
  • An overriding method must declare a compatible return type (identical primitive or covariant reference subtype) and cannot assign a more restrictive access modifier.
  • Overriding methods cannot declare new or broader checked exceptions than those declared in the superclass method, but may declare fewer checked exceptions or any unchecked exceptions.
  • Polymorphism enables a superclass reference variable to point to a subclass object, with the JVM dynamically dispatching method invocations to the overriding implementation at runtime.
  • Method overriding enables runtime polymorphism via dynamic method dispatch, whereas method overloading provides compile-time polymorphism resolved by the compiler based on method signatures.
Last updated: September 2026

8.3 Method Overriding and Polymorphic Method Execution

[!NOTE] Exam Focus: Polymorphism and method overriding represent the deepest end of Oracle's "describe the components of object-oriented programming" objective on the 1Z0-811 exam. Expect questions that test the exact checklist for valid method overriding (signature matching, covariant returns, access modifier expansion, checked exception limits), dynamic method dispatch mechanics, distinguishing compile-time reference types from runtime object types, and contrasting overriding with method overloading.

In object-oriented programming, polymorphism (originating from Greek meaning "many forms") is the ability of an object to take on many forms. In Java, this capability manifests primarily through subtyping and method overriding: a superclass reference variable can point to an instance of any of its subclasses, and method invocations on that reference automatically trigger the subclass's specialized behavior at runtime.


1. Understanding Method Overriding

Method overriding occurs when a subclass defines an instance method that possesses the exact same method name and the exact same parameter list as an instance method defined in its superclass.

When a method is overridden, the subclass provides its own customized, specialized implementation of an inherited behavior:

public class Animal {
    public void makeSound() {
        System.out.println("The animal makes a generic sound");
    }
}

public class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("The dog barks: Woof! Woof!");
    }
}

public class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println("The cat meows: Meow!");
    }
}

When makeSound() is called on a Dog instance, the JVM executes the Dog version, completely replacing the generic Animal implementation for that object.


2. The Five Strict Rules of Method Overriding

To successfully override a superclass method, the subclass method must satisfy five strict language rules defined in the Java Language Specification. Violating any of these rules results in a compile-time error:

Rule 1: Exact Same Method Signature (Name and Parameters)

The overriding method in the subclass must have the exact same method name and the exact same parameter list (same number, types, and sequence of parameters) as the superclass method.

[!CAUTION] Overriding vs. Overloading Alert: If the parameter list differs in any way (e.g., passing an int instead of a double, or adding an extra argument), you have overloaded the method, not overridden it! Overloading creates a separate method; it does not replace the inherited method.

Rule 2: Compatible Return Types (Covariant Returns)

  • Primitive Return Types: The return type must be identical. If the superclass method returns int, the overriding method must return int. It cannot return long, double, or short (no primitive type conversions or widening allowed).
  • Reference Return Types: The return type must be the same type OR a subtype (covariant return type) of the superclass method's return type:
class FoodProducer {
    public Object produce() { return new Object(); }
}

class Bakery extends FoodProducer {
    // Legal: String is a subclass of Object (Covariant return type)
    @Override
    public String produce() { return "Fresh Bread"; }
}

If FoodProducer declared public int getRating(), Bakery could not declare public double getRating() because double is not covariant with primitive int.

Rule 3: Access Modifier Cannot Be More Restrictive

The overriding method can maintain the same access level or expand visibility, but can never reduce access (it cannot assign weaker access privileges):

Superclass Access ModifierPermissible Overriding Access in SubclassForbidden (Compile Error)
publicpublicprotected, default, private
protectedprotected, publicdefault, private
Default (package-private)Default, protected, publicprivate
class SuperService {
    public void serve() { }
}

class SubService extends SuperService {
    // COMPILE ERROR: attempting to assign weaker access privileges; was public
    @Override
    protected void serve() { } 
}

Rule 4: Checked Exception Constraints

An overriding method cannot declare new or broader checked exceptions than those declared by the superclass method. However, it is fully permitted to:

  • Declare fewer checked exceptions.
  • Declare narrower (subclass) checked exceptions.
  • Declare no checked exceptions at all.
  • Declare any unchecked exceptions (RuntimeException, Error, or their subclasses).

Rule 5: Non-Overridable Methods

Not all methods can be overridden:

  1. final Methods: A method marked final cannot be overridden under any circumstances. Attempting to override a final method triggers a compile-time error: cannot override; overridden method is final.
  2. static Methods: Static methods belong to the class, not to object instances. A static method cannot be overridden. If a subclass defines a static method with the same signature as a superclass static method, the method is hidden, not overridden (compile-time static binding applies).
  3. private Methods: Private methods are not visible outside their declaring class, and therefore can never be overridden. If a subclass declares a method with the same signature as a private superclass method, it is simply a brand-new unrelated method.

3. The @Override Annotation

The @Override annotation is a compiler directive placed immediately above a method declaration. While optional, it instructs the Java compiler to verify that the annotated method truly satisfies all overriding rules against a superclass method.

public class Shape {
    public void draw() { System.out.println("Drawing a shape"); }
}

public class Circle extends Shape {
    // Typo in method name: drawe instead of draw
    @Override
    public void drawe() { // COMPILE ERROR: method does not override or implement a method from a supertype
        System.out.println("Drawing a circle");
    }
}

Without @Override, the compiler would silently treat drawe() as a completely new method in Circle, leaving draw() un-overridden. Using @Override catches signature mismatches, typos, and improper parameter types at compile time.


4. Polymorphism & Dynamic Method Dispatch in Action

The fundamental operational principle of Java polymorphism can be distilled into one golden rule:

Compile-Time vs. Runtime Separation:

  • At Compile Time: The compiler inspects the Reference Type to determine which methods and fields are accessible.
  • At Runtime: The JVM inspects the Actual Object Type on the Heap to determine which overriding method implementation to execute.

Dynamic Method Dispatch

When an instance method is called on a reference variable, the JVM uses dynamic method dispatch (runtime polymorphism). The JVM checks the actual object instance on the heap and executes the most specific overriding implementation:

public class ZooApp {
    public static void main(String[] args) {
        // Superclass reference pointing to a Subclass object!
        Animal myPet = new Dog();

        // 1. Dynamic Method Dispatch in action:
        myPet.makeSound(); // Prints: The dog barks: Woof! Woof!

        // 2. Reassigning to a different subclass:
        myPet = new Cat();
        myPet.makeSound(); // Prints: The cat meows: Meow!
    }
}

Even though the reference variable myPet is declared of type Animal, calling myPet.makeSound() invokes Dog.makeSound() and Cat.makeSound() because the underlying objects on the heap are instances of Dog and Cat.

The Reference Type Barrier (Compile-Time Limitation)

What happens if a subclass introduces a brand-new method that does not exist in the superclass?

public class Dog extends Animal {
    @Override
    public void makeSound() { System.out.println("Woof!"); }

    public void fetchBall() { System.out.println("Fetching ball..."); }
}

public class Test {
    public static void main(String[] args) {
        Animal pet = new Dog();
        pet.makeSound(); // LEGAL: makeSound() exists in Animal
        
        // pet.fetchBall(); // COMPILE ERROR: cannot find symbol method fetchBall() in class Animal
    }
}

Because the reference variable pet is of type Animal, the compiler only permits calling methods declared in Animal. Even though the underlying object is a Dog, the compiler cannot guarantee at compile time that pet will always hold a Dog. To call fetchBall(), you must explicitly downcast the reference: ((Dog) pet).fetchBall();.


5. Method Overriding vs. Method Overloading: The Definitive Comparison

Distinguishing method overriding from method overloading is one of the most prominent topics on the 1Z0-811 examination:

DimensionMethod OverridingMethod Overloading
Defining ContextAcross superclass and subclass (inheritance hierarchy)Within the same class (or inherited from superclass)
Method NameMust be exactly identicalMust be exactly identical
Parameter ListMust be identical (same types, count, and order)Must be different (different types, count, or order)
Return TypeMust be identical or covariant (subtype)Can be identical or completely different
Access ModifierCannot be more restrictiveCan be identical or completely different
Checked ExceptionsCannot declare new or broader checked exceptionsCan declare any checked or unchecked exceptions
Polymorphic NatureRuntime Polymorphism (Dynamic Method Dispatch)Compile-Time Polymorphism (Static Early Binding)
Resolved ByJVM at runtime based on actual object typeCompiler at compile time based on argument types
AnnotationEvaluated with @OverrideDoes NOT use @Override

6. Method Hiding: Instance Methods vs. Static Methods

A critical exam question involves declaring a static method in a subclass with the same signature as a static method in its superclass:

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

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

public class Main {
    public static void main(String[] args) {
        Parent ref = new Child();
        ref.display(); // What prints?
    }
}

Output: Parent static display!

Why Doesn't Child.display() Execute?

Because display() is declared static, it is hidden, not overridden. Static methods do not participate in dynamic method dispatch. The Java compiler resolves static method calls at compile time based strictly on the declared reference type (Parent), translating ref.display() into Parent.display(). Only non-static instance methods are dispatched dynamically at runtime based on the heap object type!

Loading diagram...
Compile-Time Reference Checking vs. Runtime Dynamic Method Dispatch
Test Your Knowledge

Consider the following Java program:

class Shape {
    public void draw() {
        System.out.print("Shape ");
    }
}

class Circle extends Shape {
    @Override
    public void draw() {
        System.out.print("Circle ");
    }
}

public class Test {
    public static void main(String[] args) {
        Shape s = new Circle();
        s.draw();
    }
}
What is the output when this program is executed?

A
B
C
D
Test Your Knowledge

Consider the following class hierarchy:

class SuperService {
    public Object getService() { return new Object(); }
    public int getPort() { return 8080; }
}

class SubService extends SuperService {
    // Member 1:
    @Override
    public String getService() { return "HTTP"; }

    // Member 2:
    @Override
    public long getPort() { return 8080L; }
}
What is the compilation result of attempting to compile SubService?

A
B
C
D
Test Your Knowledge

Examine the following code declarations:

class Writer {
    protected void writeMessage() {
        System.out.println("Writing");
    }
}

class SpecialWriter extends Writer {
    @Override
    void writeMessage() {
        System.out.println("Special Writing");
    }
}
What is the result of attempting to compile SpecialWriter?

A
B
C
D