Polymorphism in Java

    Atul Kabra3 min readUpdated

    Polymorphism means "many forms": the same code can work with objects of different types and behave correctly for each. In Java it comes in two flavours — compile-time polymorphism (method overloading, resolved by the compiler) and runtime polymorphism (method overriding, resolved by the JVM while the program runs). Runtime polymorphism is the powerful one: it lets you write code against a general type and have the right specific behaviour chosen automatically.

    Compile-time polymorphism (overloading)

    The same method name with different parameter lists. The compiler decides which version to call based on the arguments.

    class Printer {
        void print(int n)    { System.out.println("int: " + n); }
        void print(String s) { System.out.println("text: " + s); }
    }
    // print(5) and print("hi") call different versions — chosen at compile time.
    

    Runtime polymorphism (overriding)

    A subclass overrides a parent method; the JVM picks the actual object's version at runtime. This is also called dynamic dispatch.

    class Animal {
        String sound() { return "..."; }
    }
    class Dog extends Animal {
        @Override
        String sound() { return "Woof"; }
    }
    class Cat extends Animal {
        @Override
        String sound() { return "Meow"; }
    }
    

    Upcasting and dynamic dispatch

    You can refer to a Dog or Cat through an Animal reference. This is upcasting. When you call an overridden method, the actual object's version runs — not the reference type's.

    public class Demo {
        public static void main(String[] args) {
            Animal[] zoo = { new Dog(), new Cat(), new Animal() }; // upcast each
            for (Animal a : zoo) {
                System.out.println(a.sound()); // Woof, Meow, ... — chosen at runtime
            }
        }
    }
    

    The loop variable is typed Animal, yet each call prints the correct sound. This is what makes polymorphism so useful: you can add a new Animal subclass later and this loop keeps working unchanged.

    Why polymorphism matters

    It lets you program to an abstraction. A method that accepts an Animal works for every present and future subclass. New behaviour plugs in without touching existing code — the "open for extension, closed for modification" idea.

    // Works for ANY Animal, today or in the future.
    static void describe(Animal a) {
        System.out.println("This animal says " + a.sound());
    }
    

    Want to learn this properly?

    Join the waitlist for our courses — beginner-friendly, project-first classes in Jalgaon.

    Browse courses

    instanceof and pattern matching (modern Java)

    Sometimes you must check the real type. The instanceof pattern (standard since JDK 16) tests and casts in one step:

    static String label(Animal a) {
        if (a instanceof Dog d) {   // tests AND binds 'd' as a Dog if true
            return "A dog: " + d.sound();
        }
        return "Some animal: " + a.sound();
    }
    

    A switch can pattern-match on type, which is cleaner for several cases:

    static String classify(Animal a) {
        return switch (a) {
            case Dog d -> "dog";
            case Cat c -> "cat";
            default    -> "other";
        };
    }
    

    Common mistakes

    • Confusing overloading with overriding. Overloading is compile-time (different parameters, same class); overriding is runtime (same signature, subclass).
    • Expecting the reference type to choose the method. For overridden methods, the object's type decides — that is the whole point.
    • Hiding instead of overriding. static methods and fields are not polymorphic; they are resolved by reference type, not object type.
    • Downcasting blindly. Casting an Animal to Dog when the object is actually a Cat throws a ClassCastException. Guard with instanceof.
    • Forgetting @Override. Without it, a signature mismatch silently creates a new method that is never dispatched to.

    FAQ

    Are fields polymorphic? No. Only instance methods are dynamically dispatched. Field access uses the reference type.

    Does polymorphism slow programs down? The cost of dynamic dispatch is tiny and the JVM optimises it heavily. Design clarity wins.

    Keep going

    Next, formalise contracts with Interfaces & Abstract Classes, or revisit Inheritance in Java. All tutorials are on the Java hub; for guided practice in Jalgaon, see the Java course.


    Write flexible, extensible code at Infoplanet, Jalgaon. Join the waitlist for the Java course.

    Want to learn this properly?

    Join the waitlist for our courses — beginner-friendly, project-first classes in Jalgaon.

    Browse courses
    Atul Kabra

    Founder, Infoplanet

    Atul Kabra founded Infoplanet in 2001 and has spent over two decades teaching programming — C, C++, Java, databases and more — to students across Maharashtra.

    Related guides