OOP Basics ·
‹ Previous Next ›
⏱ 5 min read Modified: 2026-08-11

Java Inheritance and the extends Keyword

Delete a single line from the Box6 class below — the empty constructor public Box6() {} — and every subclass stops compiling, even though none of them calls that constructor. The compiler reports constructor Box6 in class Box6 cannot be applied to given types. The reason: Java silently inserts a call to the superclass no-argument constructor as the first statement of every subclass constructor.

Inheritance in Java is an object-oriented programming mechanism in which one class (the subclass) receives the fields and methods of another class (the superclass) and can add its own on top. Inheritance is declared with the extends keyword: class SubClass extends SuperClass. It lets you reuse code and build type hierarchies, which is what polymorphism runs on.

This guide walks through the extends syntax, what is actually inherited and what is not, how upcasting and downcasting work, the order in which constructors run, and how to stop a class from being extended at all.

What is inheritance in Java?

Inheritance is a mechanism where one class acquires the members of another class and may extend or specialise them. Java uses two terms for the two sides of that relationship:

  • superclass (also base class or parent class) — the class being extended;
  • subclass (also derived class or child class) — the class that extends it.

The relationship is an IS-A relationship: a Dog IS-A Animal, a Car IS-A Vehicle. This is a useful design test — if the sentence “X is a Y” sounds forced when you say it out loud, inheritance is probably the wrong tool.

Used well, inheritance lets you:

  • reuse code that already exists instead of copying it;
  • keep shared behaviour in one place, so a fix applies everywhere at once;
  • express a type hierarchy that the compiler can check;
  • enable polymorphism — treating different subclasses through one common type.

The extends keyword: syntax and example

The syntax is a single keyword in the class declaration:

class SubClass extends SuperClass {
    // class body
}

Start from a base class that describes a box:

public class Box6 {
    double width;
    double height;
    double depth;

    Box6(double w, double h, double d) {
        width = w;
        height = h;
        depth = d;
    }

    public Box6() {}

    double getVolume() {
        return width * height * depth;
    }
}

Now suppose the application needs new kinds of boxes: a ColorBox that knows its colour and a HeavyBox that knows its weight. Without inheritance you would copy the same three fields and the same getVolume() method into both classes. With extends, the shared part stays in one place:

public class ColorBox extends Box6 {
    String color;

    public ColorBox(int width, int height, int depth, String color) {
        this.width = width;
        this.height = height;
        this.depth = depth;
        this.color = color;
    }
}
public class HeavyBox extends Box6 {
    int weight;

    public HeavyBox(int width, int height, int depth, int weight) {
        this.width = width;
        this.height = height;
        this.depth = depth;
        this.weight = weight;
    }
}

Both subclasses get width, height, depth and getVolume() from Box6 and declare only what makes them different. A class can extend exactly one superclass, but a superclass can have any number of subclasses.

Worth knowing

The constructors of ColorBox and HeavyBox assign the inherited fields directly, so Java quietly inserts a call to super() — the no-argument constructor of Box6. That empty public Box6() {} exists purely to satisfy it. The idiomatic version passes the dimensions up explicitly with super(width, height, depth); the super keyword is covered in detail in the next lesson.

Access modifiers and inheritance

A subclass does not automatically see everything the superclass declares — access modifiers decide. The modifier that matters most for inheritance is protected: it opens a member to subclasses even in a different package while keeping it hidden from unrelated code.

Modifier Same class Same package Subclass in another package Any class
public yes yes yes yes
protected yes yes yes no
no modifier (default, package-private) yes yes no no
private yes no no no

Private members are not inherited at all. This is stronger than “you cannot reach them”: according to the Java Language Specification, a private field or method simply is not a member of the subclass. The field still occupies memory inside the object, but the only way to touch it is through a public or protected method of the superclass:

public class A {
    public int value1;
    private int value2;

    public int getValue2() {
        return value2;
    }
}
public class B extends A {
    public int sum() {
        // return value1 + value2; // does not compile: value2 has private access in A
        return value1 + getValue2();
    }
}

Easy to miss

The fields width, height and depth in Box6 have no modifier, so ColorBox and HeavyBox can assign them only because all three classes live in the same package. Move a subclass into another package and the same code stops compiling. For state that subclasses are meant to touch, declare it protected — or better, keep it private and expose accessor methods.

What is inherited and what is not

A short reference for every kind of class member:

Member of the superclass Inherited? Notes
public and protected fields and methods yes Usable in the subclass by name, as if declared there
Members with no modifier (package-private) only within the same package Invisible to a subclass declared in another package
private fields and methods no Present in the object, reachable only through superclass methods
Constructors no Invoked from the subclass through super(...), explicitly or implicitly
static fields and methods yes Cannot be overridden: a static method with the same signature hides the parent one

Overriding is not hiding

Instance methods are overridden (mark them with @Override) and are selected at run time from the actual object. Fields and static methods are hidden: declaring a field with the same name in a subclass does not replace the parent field, it shadows it, and which one you get is decided by the reference type at compile time. Two fields then coexist in the same object — a reliable source of confusing bugs.

Superclass reference to a subclass object: upcasting and downcasting

A variable of the superclass type may refer to an object of any subclass. This is called upcasting, and it is the foundation of polymorphism:

Box6 box = new HeavyBox(15, 10, 20, 5);
System.out.println(box.getVolume()); // 3000.0
// System.out.println(box.weight);   // does not compile: cannot find symbol

Upcasting is implicit and always safe, because every HeavyBox really is a Box6. The opposite assignment is not: not every box is heavy, so the compiler rejects it.

// HeavyBox box = new Box6(); // does not compile: incompatible types

Which members you can use is determined by the reference type, not by the object type. Through a Box6 reference the field weight is invisible, even though a HeavyBox sits on the heap. To reach subclass-specific members you need an explicit downcast:

Box6 box = new HeavyBox(15, 10, 20, 5);
HeavyBox heavy = (HeavyBox) box;
System.out.println(heavy.weight); // 5

Box6 plain = new Box6();
HeavyBox wrong = (HeavyBox) plain; // compiles, throws ClassCastException at run time

A downcast always compiles and fails only at run time, so guard it with instanceof. Since Java 16, pattern matching for instanceof does the test and the cast in one expression:

if (box instanceof HeavyBox heavy) {
    System.out.println(heavy.weight);
}

There is one important exception to the reference-type rule: if the subclass overrides a method of the superclass, the subclass version runs, because the JVM picks the method from the actual object. That is dynamic dispatch — and it is exactly why polymorphism works.

Multilevel inheritance

A hierarchy is not limited to two levels: a subclass can itself be the superclass of the next class.

public class Shipment extends HeavyBox {
    double cost;

    public Shipment(int w, int h, int d, int weight, double cost) {
        super(w, h, d, weight);
        this.cost = cost;
    }
}

The chain is Box6HeavyBoxShipment, and a Shipment object carries state from all three levels: width, height, depth from Box6, weight from HeavyBox and its own cost. Here the constructor passes the inherited values up the chain explicitly with super(w, h, d, weight) instead of assigning them field by field — see the lesson on super for the full story.

Every hierarchy ends at java.lang.Object. A class that declares no extends clause still extends Object implicitly, which is where toString(), equals(), hashCode() and the rest come from.

Constructor execution order

Constructors run top down — from the root of the hierarchy to the most derived class. The reason is the one from the opening example: the first statement of a subclass constructor is a call to a superclass constructor.

class E {
    E() { System.out.println("E"); }
}

class F extends E {
    F() { System.out.println("F"); }
}

class G extends F {
    G() { System.out.println("G"); }
}

// new G();

Output:

E
F
G

The order makes sense: a subclass builds its own state on top of the inherited state, so the superclass part must be initialised first. If the superclass has no no-argument constructor, there is nothing for the implicit super() to call, and every subclass constructor must invoke super(...) with arguments explicitly or the code will not compile.

Why Java has no multiple inheritance

A Java class extends exactly one superclass. This does not compile:

// class Shipment extends HeavyBox, ColorBox {} // does not compile

The reason is the diamond problem: if two superclasses declared a member with the same name, the compiler would have no way to decide which version the subclass inherits. Java keeps the model simple by forbidding multiple inheritance of state and implementation from classes, while allowing multiple inheritance of types through interfaces:

public class Shipment extends HeavyBox implements Comparable<Shipment>, Serializable {
    // extends - exactly one class, implements - as many interfaces as you like
}

Since Java 8 an interface may carry default methods, so implementations can collide after all — but the compiler refuses to guess. If two interfaces supply the same default method, the class must override it and, if needed, pick a version with InterfaceName.super.method().

Preventing inheritance: final and sealed

Sometimes a class must not be extended — for example so that nobody can break the invariants of an immutable type. Java gives you three levels of control:

  • final class — the class cannot be extended at all. String, Integer and LocalDate are declared this way.
  • final method — the class stays extensible, but that particular method cannot be overridden.
  • sealed class (Java 17) — the author lists exactly which classes are allowed to extend it.
public sealed class Box permits ColorBox, HeavyBox {
}

public final class ColorBox extends Box { }
public non-sealed class HeavyBox extends Box { }

Every permitted subclass must itself be declared final, sealed or non-sealed. Because the compiler then knows the complete hierarchy, a switch over the type can be checked for exhaustiveness, and the API is far easier to evolve safely.

Inheritance vs composition

Inheritance models IS-A; composition models HAS-A — instead of extending another class, an object holds it in a field and delegates work to it. The classic cautionary example lives in the JDK itself: java.util.Stack extends Vector. Along with the implementation, Stack inherited methods such as add(int index, E element), which let any caller insert an element in the middle and break the LIFO contract the class is supposed to guarantee.

A practical rule: extend a class when the subclass genuinely is a special case of the superclass and you control both classes. In every other situation prefer composition — it does not tie you to the internals of someone else's implementation. Class relationships are covered in more depth in the lesson on composition and aggregation.

Where developers get tripped up

  • Expecting access to the parent's private fields. They are not inherited; go through a public or protected accessor.
  • Confusing the reference type with the object type. Fields and overload resolution are decided by the declared type at compile time; overridden methods are dispatched on the actual object at run time.
  • Forgetting that the superclass needs a no-argument constructor. If it has none, every subclass constructor must call super(...) with arguments explicitly.
  • Downcasting without a check. The cast always compiles and blows up later with ClassCastException; instanceof prevents it.
  • Extending a class just to reuse its code. If “X is a Y” is not true, you want a field, not extends.
  • Redeclaring a field that already exists in the superclass. That hides the parent field instead of replacing it, and the object ends up holding two values under one name.

For the formal rules, see Oracle's tutorial on subclasses and inheritance and JLS §8.2, Class Members.

Frequently asked questions

Are private fields and constructors inherited in Java?

No. Neither private members nor constructors are inherited. A private field physically exists inside the subclass object, but it cannot be referenced by name — only a public or protected method of the superclass can expose it. A superclass constructor never becomes a constructor of the subclass either; it is merely invoked from one through super(...).

Why can a Java class extend only one class?

Because of the diamond problem: with two superclasses declaring the same method or field, the compiler could not decide which version to inherit. So extends accepts exactly one class, and multiple inheritance of types is expressed through interfaces, which you can list without limit after implements.

What is the difference between extends and implements?

extends inherits state and implementation from a single class, while implements commits a class to fulfilling an interface contract. A class can implement many interfaces, and since Java 8 interfaces may provide default methods with a body, but they still hold no instance state. Note that an interface itself extends other interfaces with extends, not implements.

Can a subclass make an inherited method less visible?

No. An overriding method may widen access but never narrow it: turning a public method into protected or private is a compile-time error, because code holding a superclass reference must keep working. The same idea applies to checked exceptions — an override may throw fewer or narrower ones, never new broader ones — while the return type may be narrowed, which is called a covariant return type.

Key takeaways

  • Inheritance is declared with extends and expresses an IS-A relationship between a subclass and its superclass.
  • public and protected members are inherited, private members are not, and constructors are never inherited.
  • A variable of the superclass type can hold a subclass object (upcasting); the reference type decides which members are visible, the object type decides which overridden method runs.
  • Constructors execute from the root of the hierarchy downwards, because a subclass constructor implicitly calls super().
  • Java has no multiple inheritance of classes — interfaces fill that role.
  • Use final or sealed to close a class, and prefer composition whenever “X is a Y” does not hold.

Comments

Please log in or register to have a possibility to add comment.