Upcasting and Downcasting

Naveen AnamaneniUpdated · 5 min read

At a hospital reception, everyone is simply a "visitor": the receptionist only needs your name and time. Inside, a doctor is still a doctor and can do things a visitor can't. In Java, a SavingsAccount object can be held in an Account variable, and then you can only use Account features through it. That's upcasting. Getting the savings features back means downcasting, which needs care, because not every account is a savings account.

The Reference Type Decides What You Can Call

Same object; the variable's type decides what's visible
UpcastingDowncasting
DirectionChild type → parent typeParent type → child type
ExampleAccount a = new SavingsAccount(...);SavingsAccount s = (SavingsAccount) a;
Cast needed?No, automaticYes, explicit (SavingsAccount)
Always safe?Yes: a savings account is an accountNo: fails if the object isn't really that type
What you can callOnly the parent's membersThe child's members too

Up and Down in Code

Cast.java▶ Try it
public class Cast {
    static class Account {
        String type() { return "Account"; }
    }
 
    static class Savings extends Account {
        @Override String type() { return "Savings"; }
        double rate() { return 3.5; }
    }
 
    static class Current extends Account {
        @Override String type() { return "Current"; }
    }
 
    public static void main(String[] args) {
        Account a = new Savings();
        System.out.println(a.type());
        Savings s = (Savings) a;
        System.out.println("Rate: " + s.rate());
 
        Account[] accounts = {new Savings(), new Current()};
        for (Account acc : accounts) {
            if (acc instanceof Savings sv) {
                System.out.println(acc.type() + " earns " + sv.rate() + "%");
            } else {
                System.out.println(acc.type() + " earns no interest");
            }
        }
    }
}
Output
Savings
Rate: 3.5
Savings earns 3.5%
Current earns no interest

How it works

  1. Line 16: upcasting happens automatically: a Savings object stored in an Account variable.
  2. Line 17: a.type() still prints "Savings": the object's real class decides which overridden method runs (dynamic dispatch, see "Runtime Polymorphism and Dynamic Method Dispatch").
  3. Through a, you can't call rate(); Account doesn't have it. Line 18 downcasts to get it back.
  4. Lines 23 to 27 use instanceof with a pattern (Java 16+): if acc is a Savings, it's checked and cast into the new variable sv in one step. Otherwise, no cast is attempted.

Try it yourself: write Savings x = (Savings) accounts[1];. What happens when you run it?

When a Downcast Fails

Casting an object to a type it isn't compiles, but fails at runtime:

Terminal output
Exception in thread "main" java.lang.ClassCastException: class Cast$Current cannot be cast to class Cast$Savings

For unrelated types, the compiler catches it straight away: (Integer) "a" gives incompatible types: String cannot be converted to Integer.

Why Upcast at All?

Upcasting lets one piece of code work with every kind of account: an Account[] holding savings and current accounts, a method void audit(Account a) that accepts any of them, a List<Account>. Each object still behaves as its real type when overridden methods are called. That combination, one parent-type view and many child behaviors, is polymorphism, the subject of the Polymorphism chapter.

Common Mistakes

Casting without checking. It throws ClassCastException for the wrong subtype.

Thinking upcasting changes the object. The object keeps all its fields; only the variable's view is narrower.

Expecting fields to behave like methods. Methods are chosen by the object's real type; fields by the variable's type. Another reason not to hide fields.

Interview Corner

What is upcasting and downcasting?

Upcasting assigns a subclass object to a superclass reference; it's implicit and always safe, but only the superclass's members are accessible through that reference. Downcasting converts a superclass reference back to a subclass type; it needs an explicit cast, and throws ClassCastException at runtime if the object isn't actually of that subclass. instanceof checks first.

What does instanceof with a pattern do?

if (obj instanceof Savings s) checks the object's type and, if it matches, declares s already cast to Savings for use in that branch. It replaces the older two-step check-then-cast and was added in Java 16.

Practice

  1. Easy: Store a Manager in an Employee variable and call a method that only Employee has.
  2. Harder: Write static double totalInterest(Account[] accounts) that adds up rate() only for savings accounts, using an instanceof pattern.

Quick check: Upcasting and downcasting

4 questions
1Is upcasting automatic?
2Account a = new Savings(); Can you call a.rate() (a Savings-only method)?
3What does (Savings) aCurrentAccount do at runtime?
4What does if (acc instanceof Savings sv) give you?

Key Takeaways

Was this tutorial helpful?

Lesson 120 of 482