Skip to content
Java abstraction 6 min read

Abstract Class

An abstract class is a class that cannot be instantiated directly — it exists to be extended. It lets you define a common blueprint with some behavior already filled in and some left intentionally incomplete for subclasses to provide.

Why Abstract Classes Exist

Sometimes you want to model a concept that is too general to be concrete. A Shape has an area, but there is no single formula that covers every shape. An abstract class lets you say: “every Shape must know how to compute its area — but I’m not going to write that logic here.”

This is the heart of abstraction: hiding irrelevant details and enforcing a contract.

Declaring an Abstract Class

Use the abstract keyword before the class keyword. An abstract class can have:

  • Abstract methods — declared without a body (subclasses must implement them)
  • Concrete methods — fully implemented, shared by all subclasses
  • Fields, constructors, and static members — just like any regular class
abstract class Shape {
    String color; // regular field

    // Constructor — yes, abstract classes can have constructors!
    Shape(String color) {
        this.color = color;
    }

    // Abstract method — no body, subclass must implement
    abstract double area();

    // Concrete method — shared by all shapes
    void display() {
        System.out.println("Color: " + color + ", Area: " + area());
    }
}

Note: If a class contains even one abstract method, the class itself must be declared abstract. A concrete class that extends an abstract class must implement all abstract methods — or be declared abstract itself.

Implementing an Abstract Class

A subclass extends the abstract class using extends and overrides every abstract method:

class Circle extends Shape {
    double radius;

    Circle(String color, double radius) {
        super(color); // call abstract class constructor
        this.radius = radius;
    }

    @Override
    double area() {
        return Math.PI * radius * radius;
    }
}

class Rectangle extends Shape {
    double width, height;

    Rectangle(String color, double width, double height) {
        super(color);
        this.width = width;
        this.height = height;
    }

    @Override
    double area() {
        return width * height;
    }
}

public class Main {
    public static void main(String[] args) {
        Shape c = new Circle("Red", 5);
        Shape r = new Rectangle("Blue", 4, 6);

        c.display();
        r.display();
    }
}

Output:

Color: Red, Area: 78.53981633974483
Color: Blue, Area: 24.0

Notice that c and r are declared as type Shape. The JVM dispatches area() to the correct subclass at runtime — this is runtime polymorphism in action.

Tip: You cannot write new Shape("Red") — that will cause a compile-time error: “Shape is abstract; cannot be instantiated.”

Abstract Classes with Constructors

Even though you can’t create an instance of an abstract class directly, constructors are still useful. Every subclass constructor calls super(...), which triggers the abstract class’s constructor to initialize shared fields.

abstract class Animal {
    String name;

    Animal(String name) {
        this.name = name;
        System.out.println("Animal created: " + name);
    }

    abstract void makeSound();
}

class Dog extends Animal {
    Dog(String name) {
        super(name); // triggers Animal's constructor
    }

    @Override
    void makeSound() {
        System.out.println(name + " says: Woof!");
    }
}

Output:

Animal created: Buddy
Buddy says: Woof!

Concrete Methods in Abstract Classes

One major advantage over interfaces is that abstract classes can contain real, implemented methods. This lets you provide default behavior that subclasses can use or override:

abstract class Vehicle {
    int speed;

    Vehicle(int speed) {
        this.speed = speed;
    }

    abstract String fuelType(); // must override

    // Shared concrete behavior
    void accelerate(int amount) {
        speed += amount;
        System.out.println("Speed: " + speed + " km/h");
    }
}

class ElectricCar extends Vehicle {
    ElectricCar(int speed) {
        super(speed);
    }

    @Override
    String fuelType() {
        return "Electric";
    }
}

Rules at a Glance

RuleDetails
Keywordabstract class ClassName
Can be instantiated?No
Can have abstract methods?Yes (zero or more)
Can have concrete methods?Yes
Can have constructors?Yes
Can have static methods?Yes
Can have final methods?Yes (subclasses cannot override them)
Can extend another class?Yes (including another abstract class)
Can implement interfaces?Yes

Warning: A final abstract class is a compile error — final prevents subclassing, which makes an abstract class useless.

Abstract Class vs Interface — When to Choose Which

Both enforce contracts. Use an abstract class when:

  • You want to share code (fields, constructors, concrete methods) across subclasses
  • Your subtypes represent a genuine “is-a” hierarchy (a Dog is an Animal)
  • You need non-public members

Use an interface when you want pure behavior contracts that any class can adopt regardless of hierarchy. See the full comparison at Abstract Class vs Interface.

FeatureAbstract ClassInterface
Multiple inheritanceNo (single extends)Yes (multiple implements)
FieldsAny typeOnly public static final
ConstructorsYesNo
Access modifiersAnyMethods are public by default
Default methods (Java 8+)Concrete methods alwaysdefault keyword required

The Template Method Pattern

Abstract classes shine in the Template Method design pattern: the abstract class defines the overall algorithm skeleton in a concrete method, calling abstract steps that subclasses fill in:

abstract class DataProcessor {
    // Template method — the algorithm skeleton
    final void process() {
        readData();
        processData();
        writeData();
    }

    abstract void readData();
    abstract void processData();

    // Default step — subclasses may override if needed
    void writeData() {
        System.out.println("Writing output to default location.");
    }
}

class CSVProcessor extends DataProcessor {
    @Override
    void readData() { System.out.println("Reading CSV file..."); }

    @Override
    void processData() { System.out.println("Parsing CSV rows..."); }
}

public class Main {
    public static void main(String[] args) {
        new CSVProcessor().process();
    }
}

Output:

Reading CSV file...
Parsing CSV rows...
Writing output to default location.

The final on process() means subclasses cannot change the order of steps — only the steps themselves.

Under the Hood

When the JVM loads an abstract class, it creates a normal Class object — the abstract flag is simply stored in the class’s access flags in the bytecode (ACC_ABSTRACT). Abstract methods have the same flag set, and their method entries in the constant pool have no Code attribute.

At runtime, the JVM uses a vtable (virtual method table) for each concrete subclass. Every abstract method slot gets replaced by the concrete implementation provided by the subclass. Calling shape.area() does not go through a branch or lookup — it goes directly to the correct vtable slot, making virtual dispatch very fast. Read more in vtable & Dynamic Dispatch.

Memory-wise, you cannot allocate an instance of an abstract class, so the JVM never reserves heap space for an “abstract object.” The constructor bytecode is still compiled and stored; it is simply called via invokespecial from a concrete subclass constructor.

Tip: Use javap -verbose ClassName (see the javap tool) on both an abstract class and one of its concrete subclasses to see the ACC_ABSTRACT flag and how vtable slots are filled.

  • Abstraction — the broader OOP concept that abstract classes implement
  • Interface — the other main tool for enforcing contracts in Java
  • Abstract Class vs Interface — a detailed side-by-side comparison to help you choose
  • Runtime Polymorphism — how the JVM dispatches method calls to concrete subclasses at runtime
  • Inheritance — the foundation that makes abstract classes possible
  • final Keyword — how final methods inside abstract classes prevent overriding
Last updated June 13, 2026
Was this helpful?