Polymorphism in Java

Polymorphism in Java: Overloading vs Overriding (2026 Complete Guide)

Polymorphism in Java: Overloading vs Overriding

Polymorphism is one of the four fundamental principles of Object-Oriented Programming (OOP). It allows an object or method to take multiple forms — the same method name can behave differently based on the context.

In this guide, you will learn both types of polymorphism in Java — overloading (compile-time) and overriding (runtime) — with complete examples, rules, and common gotchas.

What is Polymorphism?

Polymorphism is a major principle of Object-Oriented Programming. It is used to perform a single task in different ways or different styles.

The word comes from two Greek words: Poly (many) and Morphs (forms) — meaning "many forms".

Real-world example: A Dog is a form of Animal, and a Lion is also a form of Animal. Both are animals, but each has a different behavior.

In Java, polymorphism can be achieved in two ways:

  • Overloading — compile-time polymorphism
  • Overriding — runtime polymorphism

Types of Polymorphism

Java supports two types of polymorphism:

  1. Compile-time polymorphism (static binding) — achieved through method overloading
  2. Runtime polymorphism (dynamic binding) — achieved through method overriding

1. Overloading (Compile-Time Polymorphism)

Overloading is possible within a single class. It means having multiple methods or constructors with the same name but different parameters.

class A {
    // Constructor overloading
    A() {}
    A(int i) {}

    // Method overloading
    void add(int i, int j) {}
    void add(float i, float j) {}

    // Static method overloading
    static void multiply(int i, int j) {}
    static void multiply(float i, float j) {}
}

Overloading is also called static binding, early binding, or compile-time polymorphism.

Why is it called compile-time polymorphism?

class A {
    static void add(int i, int j) {
        System.out.println(i + j);
    }

    public static void main(String[] args) {
        // Pass int → works fine
        add(1, 1);   // Output: 2

        // Pass float → compile-time error
        add(1.2f, 1.2f);   // Compile-time error
    }
}

When we call add(1, 1), the compiler finds a matching add(int, int) method. When we call add(1.2f, 1.2f), the compiler looks for add(float, float) — it does not exist, so the compiler throws an error at compile time. That is why it is called compile-time polymorphism.

Overloading — Rules & Examples

Rule 1: Can we change the return type of overloaded methods?

Yes.

class A {
    static int add(int i, int j) {
        return i + j;
    }

    static float add(float i, float j) {
        return i + j;
    }

    public static void main(String[] args) {
        int out1 = add(1, 1);       // Output: 2
        float out2 = add(1.2f, 1.2f);  // Output: 2.4
    }
}

Note: The compiler chooses the method based on the type of the arguments. Integer literals are int by default. Fractional literals are double by default — add an f suffix to make them float.

Rule 2: Can the main method be overloaded?

Yes. The JVM always calls main(String[] args), but you can have other overloads.

class A {
    public static void main(String[] args) {
        System.out.println("main method called by JVM by default");
    }

    public static void main(String args) {
        System.out.println("main method called explicitly");
    }
}

Output:

main method called by JVM by default

Rule 3: Can we change the access modifiers of overloaded methods?

Yes.

class A {
    private void show(int i) {
        System.out.println("int " + i);
    }

    public void show(float i) {
        System.out.println("float " + i);
    }

    public static void main(String[] args) {
        A a = new A();
        a.show(1);
    }
}

Output:

int 1

Rule 4: Can constructors be overloaded?

Yes.

class A {
    A() {
        System.out.println("Hello");
    }

    A(int i) {
        System.out.println("Hi");
    }

    public static void main(String[] args) {
        new A();      // Output: Hello
        new A(10);    // Output: Hi
    }
}

Rule 5: Can final methods be overloaded?

Yes. final prevents overriding, not overloading.

class A {
    final void show() {
        System.out.println("Hello");
    }

    final void show(int i) {
        System.out.println("Hi");
    }

    public static void main(String[] args) {
        A a = new A();
        a.show();     // Output: Hello
        a.show(1);    // Output: Hi
    }
}

Rule 6: Can methods have the same parameter type but different length?

Yes. Different number of parameters is also a valid overload.

class A {
    void show(int i) {
        System.out.println("Hello");
    }

    void show(int i, int j) {
        System.out.println("Hi");
    }

    public static void main(String[] args) {
        A a = new A();
        a.show(1);      // Output: Hello
        a.show(1, 1);   // Output: Hi
    }
}

2. Overriding (Runtime Polymorphism)

Overriding requires at least two classes with a parent-child relationship.

class A {
}

class B extends A {
}

Overriding means the child class provides its own implementation of a method already defined in the parent class, using the same signature.

class Pen {
    void write() {
        // parent implementation
    }
}

class BallPen extends Pen {
    void write() {
        System.out.println("Write to copy");
    }
}

Overriding — Rules & Examples

Rule 1: Only non-static methods can be overridden

class Pen {
    // non-static method
    void write() {
    }
}

class BallPen extends Pen {
    // non-static method
    void write() {
        System.out.println("Write to copy");
    }
}

Static methods are not overridden — they are hidden. See Rule 7 below.

Rule 2: To prevent overriding, make the method final

class Pen {
    final void write() {
        // cannot be overridden
    }
}

class BallPen extends Pen {
    // Child cannot override a final method
}

Rule 3: Main method can never be overridden

main is static, and static methods are not inherited — so they cannot be overridden. This is why the child main does not override the parent main.

class Pen {
    public static void main(String[] args) {
        System.out.println("Parent main method");
    }
}

class BallPen extends Pen {
    public static void main(String[] args) {
        System.out.println("Child main method");
    }
}

class Test {
    public static void main(String[] args) {
        BallPen.main(args);
        Pen.main(args);
    }
}

Output:

Child main method
Parent main method

Rule 4: Can we change the access modifier of an overridden method?

Yes, but only to a wider access level. You cannot reduce visibility.

class A {
    void show() {
        System.out.println("Hello show");
    }
}

class Test extends A {
    public void show() {   // wider access — OK
        System.out.println("Hi show");
    }
}

Invalid: Making the overridden method private or protected when the parent method is public.

Rule 5: Covariant return types

The overridden method can return a subclass of the return type in the parent method. This is called a covariant return type (Java 5+).

class A {
    Number add(int i, int j) {
        return i + j;
    }
}

class B extends A {
    @Override
    Integer add(int i, int j) {   // Integer is a subclass of Number — OK
        return i + j;
    }
}

Note: Integer, Float, Double, and Long all extend Number. So a child method returning Integer can override a parent method returning Number.

Rule 6: Constructors can never be overridden

Constructors are not inherited, so they cannot be overridden. However, when a child object is created, the parent constructor runs first.

class Pen {
    Pen() {
        System.out.println("Parent constructor");
    }
}

class BallPen extends Pen {
    BallPen() {
        System.out.println("Child constructor");
    }
}

class Test {
    public static void main(String[] args) {
        new BallPen();
    }
}

Output:

Parent constructor
Child constructor

Rule 7: Static methods achieve method hiding (not overriding)

When a child class declares a static method with the same signature as a parent static method, it hides the parent method instead of overriding it.

class Pen {
    public static void show() {
        System.out.println("Parent show method");
    }
}

class BallPen extends Pen {
    public static void show() {
        System.out.println("Child show method");
    }
}

class Test {
    public static void main(String[] args) {
        Pen.show();       // Parent show method
        BallPen.show();   // Child show method
    }
}

Rule 8: To prevent method hiding, make it final

class Pen {
    public final static void show() {
        System.out.println("Parent show method");
    }
}

class BallPen extends Pen {
    // Child can never hide a final static method
}

Rule 9: Runtime polymorphism with method overriding

Overriding is called runtime polymorphism, dynamic binding, or late binding because the JVM decides which method to invoke based on the actual object at runtime.

class Mobile {
    public void call() {
        System.out.println("mobile call");
    }
}

class Oppo extends Mobile {
    public void call() {
        System.out.println("oppo mobile call");
    }
}

class Samsung extends Mobile {
    public void call() {
        System.out.println("samsung mobile call");
    }
}

class User {
    private Mobile mobile;

    public void setMobile(Mobile mob) {
        mobile = mob;
    }

    public Mobile getMobile() {
        return mobile;
    }
}

public class Test {
    public static void main(String[] args) {
        User user = new User();

        user.setMobile(new Oppo());
        user.getMobile().call();      // oppo mobile call

        user.setMobile(new Samsung());
        user.getMobile().call();      // samsung mobile call
    }
}

Why this works: the User class holds a Mobile reference, but the actual object could be Oppo or Samsung. The JVM decides which call() to run based on the object's real type at runtime.

Overloading vs Overriding — Comparison

Feature Overloading Overriding
Class requirement Same class Parent and child class
Method signature Same name, different parameters Same name, same parameters
Binding type Compile-time (static) Runtime (dynamic)
Return type Can differ freely Covariant only (subclass allowed)
Access modifier Can differ freely Can only widen
static methods Can be overloaded Cannot be overridden (only hidden)
final methods Can be overloaded Cannot be overridden
Constructors Can be overloaded Cannot be overridden
Polymorphism type Compile-time Runtime

Frequently Asked Questions

What is polymorphism in Java?

Polymorphism in Java is the ability of an object to take many forms. It allows the same method or operator to behave differently based on the context. Polymorphism is one of the four fundamental principles of Object-Oriented Programming (OOP).

What is the difference between overloading and overriding in Java?

Overloading means having multiple methods with the same name but different parameters within the same class. It is resolved at compile time. Overriding means redefining a parent class method in a child class with the same signature. It is resolved at runtime.

Can we override a static method in Java?

No. Static methods cannot be overridden — they are hidden. When a subclass declares a static method with the same signature, it hides the parent method. The method that runs depends on the reference type, not the object type.

What is a covariant return type in Java?

Covariant return type allows an overriding method to return a subclass of the return type declared in the parent method. For example, if the parent returns Number, the child can return Integer. It was introduced in Java 5.

Why is overriding called runtime polymorphism?

Overriding is called runtime polymorphism because the JVM decides which method to invoke based on the actual object type at runtime, not the reference type. This is also known as dynamic binding or late binding.

Conclusion

Polymorphism is a core OOP principle that lets the same method behave differently based on context. In Java, it takes two forms:

  • Overloading — same method name with different parameters, resolved at compile time.
  • Overriding — same method signature in a child class, resolved at runtime.

Remember: static and final methods can be overloaded but not overridden. Constructors can be overloaded but not overridden. Covariant return types let you return a subclass from an overridden method.

Related topics: Java Encapsulation, Java Inheritance, Java Abstraction, Java this Keyword, Java super Keyword, Java OOP Concepts, Java Interface

No comments: