Covariant Return Type in Java

Naveen AnamaneniUpdated · 5 min read

A Document class has a copy() method that returns a Document. An Invoice extends Document, and when you copy an invoice you get an invoice back, so it would be silly for callers to receive a plain Document and cast it. Since Java 5, an overriding method may declare a more specific return type than the method it overrides. This is called a covariant return type: the return type "varies along with" the class.

A More Specific Return Type

Copies.java▶ Try it
public class Copies {
    static class Document {
        String title;
        Document(String title) { this.title = title; }
        Document copy() { return new Document(title); }
    }
 
    static class Invoice extends Document {
        long amount;
        Invoice(String title, long amount) {
            super(title);
            this.amount = amount;
        }
 
        @Override
        Invoice copy() { return new Invoice(title, amount); }
    }
 
    public static void main(String[] args) {
        Invoice original = new Invoice("INV-7 for Srihitha", 4500);
        Invoice dup = original.copy();
        System.out.println(dup.title + ", Rs. " + dup.amount);
        System.out.println(dup != original);
    }
}
Output
INV-7 for Srihitha, Rs. 4500
true

How it works

  1. Document.copy() returns Document. Invoice.copy() overrides it but returns Invoice, a subtype of Document. That's allowed.
  2. original.copy() is called through an Invoice variable, so the compiler knows it returns an Invoice. No cast is needed to read dup.amount.
  3. The override still honors the parent's promise: an Invoice is a Document, so code that calls copy() through a Document variable still gets a Document.

Try it yourself: call copy() through a Document doc = original; variable. What type does the compiler say it returns? Can you read .amount without a cast?

The Limits

Parent returnsOverride returnsAllowed?
DocumentInvoice (a subclass)Yes
ObjectStringYes
List<String>ArrayList<String>Yes
InvoiceDocument (a superclass)No: that's wider, not narrower
intlongNo: primitives must match exactly
voidintNo

The primitive rule produces this error: return type long is not compatible with int.

Where You See It

  • clone(): Object.clone() returns Object, but your own class can override it to return its own type, so callers skip the cast (see "Object Cloning in Java").
  • Builders and fluent APIs: a child builder's methods return the child builder, so chained calls keep the specific type.
  • StringBuilder.append returns StringBuilder, overriding a method from its parent that returns a more general type.

Common Mistakes

Narrowing a primitive return type. int to long or short isn't covariance; primitives must match.

Widening instead of narrowing. An override can't return a supertype of the parent's return type.

Forgetting the specific type is visible only through the child type. Through a Document variable, copy() still returns Document.

Under the Hood

The JVM identifies a method by its name, parameter types and return type. So how does a call through a Document variable reach Invoice copy()? The compiler adds a hidden bridge method to Invoice:

javap -p -c Copies$Invoice (trimmed)
  Copies$Invoice copy();
    Code:
         0: new           #8                  // class Copies$Invoice
        ...
        15: areturn
 
  Copies$Document copy();
    Code:
         0: aload_0
         1: invokevirtual #20                 // Method copy:()LCopies$Invoice;
         4: areturn

The second copy(), returning Document, is the bridge. It overrides the parent's method at the bytecode level and simply calls the real Invoice copy(). You never write it or see it in your source.

Interview Corner

What is a covariant return type?

It's the rule, since Java 5, that an overriding method may return a subtype of the overridden method's return type. For example, Document copy() can be overridden as Invoice copy(). It removes casts for callers using the subclass type. It applies only to reference types; primitive return types must match exactly. The compiler generates a bridge method so the override still matches the parent's method at the bytecode level.

Practice

  1. Easy: Create Animal with Animal baby() and Dog overriding it as Dog baby(). Call a Dog-only method on the result without a cast.
  2. Harder: Override clone() in a Point class (implement Cloneable) so it returns Point instead of Object, and make it public.

Quick check: Covariant return type

4 questions
1The parent returns Document. Which can the override return?
2Do covariant returns work with primitives?
3Since which Java version are covariant returns allowed?
4What does the compiler add to support them?

Key Takeaways

Was this tutorial helpful?

Lesson 127 of 482