Course contents

Abstract classes in Java

The abstract keyword on classes and methods, why abstract classes have constructors, the template method pattern, illegal modifier combinations, what the JVM stores, and how to choose between an abstract class and an interface.

FreeMust know for interviewsLesson15 min readCoreReviewed Oct 2026 · Java 21
On this page
  1. Picture it first: a blueprint with a few blanks left in
  2. Abstract classes under the hood
  3. Real world: partial implementations in the JDK
  4. Advanced: the rules interviewers probe
  5. How to say it in the interview

🏗️ What is an abstract class?

An abstract class is a class marked with the abstract keyword that cannot be instantiated on its own and exists to be extended. It can hold fields, constructors and fully written methods, plus abstract methods: signatures with no body that every concrete subclass must implement. For example, an abstract PaymentProcessor can own the whole "validate, charge, audit" flow and leave only charge() blank, so CardProcessor and UpiProcessor each fill in that one step.

Abstract classes look like a beginner topic, and that is exactly why interviewers use them. "Can an abstract class have a constructor?" sounds like a trick question, and the answer tells me whether you understand object creation or only remember keywords. The follow-ups go deeper fast: why is abstract final illegal, what happens if a constructor calls an abstract method, and when would you still pick an abstract class now that interfaces have default methods?

What you’ll learn

  • What abstract means on a class and on a method, and why you can't new an abstract class
  • Why an abstract class has a constructor, and the bug you get by calling an abstract method from it
  • How the template method pattern uses a final method plus abstract hooks, as in AbstractList
  • Which combinations (abstract final, abstract static, abstract private) are illegal and why
  • What ACC_ABSTRACT, empty vtable slots and AbstractMethodError tell you about the JVM view
  • A clear rule for abstract class vs interface after Java 8 default methods

For this lesson, we'll focus on the abstract keyword on classes and methods, why abstract classes have constructors, the template method pattern, the modifier combinations the compiler rejects, what the JVM actually stores, and a clear way to choose between an abstract class and an interface.

Below the line (out of scope)

The 30-second answer

An abstract class is a partially implemented type: it can't be instantiated with new, but it can have state, constructors, concrete methods and abstract methods that subclasses must implement. Its constructor runs as part of every subclass's construction through the super(...) chain, which is why it exists. The classic use is the template method pattern: a final method fixes the algorithm and calls abstract or overridable steps. abstract can't combine with final, static or private, because each of those prevents overriding. I choose an abstract class when related types share state and construction logic; I choose an interface when I need a capability that unrelated classes can implement, often several at once.

Interview radar

Asked very often: Can an abstract class have a constructor and state, and when do you pick it over an interface? Senior rounds probe: Why is abstract final illegal, and what breaks when a superclass constructor calls an abstract method the subclass has not finished initialising? Production angle: A template-method base class called an overridable step before the subclass field was set, producing a NullPointerException only in production where a real subclass was wired in.

01Picture it first: a blueprint with a few blanks left in

Think of a builder's standard house plan. The plan fixes the foundation, the plumbing and the order of work, but it leaves a few boxes marked "client chooses": the kitchen layout, the flooring. Nobody can live in the plan itself. You only get a house once someone fills in every blank, and every house built from that plan still gets the same foundation poured in the same order.

That is an abstract class. The fixed parts are concrete methods and fields. The blanks are abstract methods. The "nobody lives in the plan" rule is the ban on new. The diagram below shows the payment version we'll build in a moment: one abstract parent that owns the flow, and two concrete subclasses that each fill in one blank.

Fig. 1

An abstract PaymentProcessor owns the final process method, a shared validate step and an abstract charge step; CardProcessor and UpiProcessor each implement charge, and only the concrete subclasses can be created with new

An abstract PaymentProcessor owns the final process method, a shared validate step and an abstract charge step; CardProcessor and UpiProcessor each implement charge, and only the concrete subclasses can be created with new

Notice that the arrows point upward: subclasses depend on the parent's flow, never the other way round. The parent calls charge() without knowing which subclass will answer, and that inversion is the whole point of the pattern.

02Abstract classes under the hood

Try it yourself first

Before reading on, sketch a PaymentProcessor that every payment method must go through. Decide which method should be final, which should be abstract, and what the constructor is for if nobody can call new PaymentProcessor().

Here's how I'd write it:

public abstract class PaymentProcessor {
    private final String name;
    private final List<String> audit = new ArrayList<>();

    protected PaymentProcessor(String name) {      // runs for every subclass
        this.name = Objects.requireNonNull(name);
    }

    public final Receipt process(Order order) {     // the template: fixed order of steps
        validate(order);
        String txnId = charge(order.amount());
        audit.add(name + " charged " + order.amount());
        return new Receipt(order.id(), txnId);
    }

    protected void validate(Order order) {          // hook with a sensible default
        if (order.amount().signum() <= 0) {
            throw new IllegalArgumentException("amount must be positive");
        }
    }

    protected abstract String charge(BigDecimal amount);   // the blank
}

final class CardProcessor extends PaymentProcessor {
    CardProcessor() { super("card"); }

    @Override
    protected String charge(BigDecimal amount) { return "card-" + amount; }
}

PaymentProcessor p = new CardProcessor();
System.out.println(p.process(new Order("o-1", new BigDecimal("499.00"))));
// prints: Receipt[orderId=o-1, txnId=card-499.00]

Notice four things:

  • process() is final. Subclasses can change a step, but they can't reorder the steps or skip validation and auditing. That is the "template" in template method.
  • charge() is abstract and protected. It's a step for subclasses, not an entry point for callers. An abstract method has no body, not even {}, and ends with a semicolon.
  • validate() is a hook. It has a default body, so subclasses override it only when they need to. A good abstract class keeps the abstract surface small: one or two blanks, many hooks.
  • The variable type is the abstract class. PaymentProcessor p can hold any concrete subclass, and the call to charge() inside process() dispatches to the real object at runtime.

Why an abstract class has a constructor

You can't write new PaymentProcessor("card"); the compiler says PaymentProcessor is abstract; cannot be instantiated. Yet the class has a constructor, and it must. Every object of a subclass contains the parent's fields, and something has to initialise them. When you write new CardProcessor(), the JVM allocates one object big enough for every field in the hierarchy, then runs constructors from the top down: Object(), then PaymentProcessor(String), then the body of CardProcessor().

The order of work during that single new is shown in the next figure, including the trap at step 3.

Fig. 2

Four numbered steps for new CardProcessor(): allocate one object with all fields zeroed, run Object's constructor, run PaymentProcessor's constructor and field initialisers, then run CardProcessor's own field initialisers and constructor body

Four numbered steps for new CardProcessor(): allocate one object with all fields zeroed, run Object's constructor, run PaymentProcessor's constructor and field initialisers, then run CardProcessor's own field initialisers and constructor body

So the abstract class constructor is how the parent enforces its invariants. Here, name can never be null, and every subclass is forced to supply one because the only constructor takes a String. Making the constructor protected documents that it's meant for subclasses; it doesn't change much in practice, since nobody else can call it anyway. We cover the full chain in A guide to constructors in Java and the super(...) call in The super keyword.

Try it yourself first

Predict the output. The parent's constructor calls an abstract method that the subclass implements by reading its own field.

abstract class Notifier {
    Notifier() {
        System.out.println("channel = " + channel());
    }
    abstract String channel();
}

class SmsNotifier extends Notifier {
    private String channel = "sms";

    @Override
    String channel() { return channel; }
}

new SmsNotifier();
// prints: channel = null

It prints null. The Notifier constructor runs before SmsNotifier's field initialisers, so when channel() dispatches to the subclass (and it does: dispatch uses the real object, even during construction), the field still holds its zeroed default. This is why Effective Java says constructors must never call overridable methods. One extra twist for the curious: if you declare the field private final String channel = "sms";, it prints sms, because a final field initialised with a constant expression is a constant variable and javac inlines the literal into channel().

What the JVM stores

abstract survives compilation as a flag. Run javap -v PaymentProcessor.class and you'll see:

abstract class PaymentProcessor
  flags: (0x0420) ACC_SUPER, ACC_ABSTRACT
...
  protected abstract java.lang.String charge(java.math.BigDecimal);
    flags: (0x0404) ACC_PROTECTED, ACC_ABSTRACT

Notice three things in that output:

  1. ACC_ABSTRACT (0x0400) on the class tells the JVM to refuse instantiation. If hand-written bytecode or a stale class file executes new on it, that new instruction throws InstantiationError.
  2. The abstract method has no Code attribute. There is literally no bytecode for it. The class's method table still reserves a vtable slot for charge(), and each concrete subclass's vtable puts its own implementation in that slot.
  3. Calls look the same as any other virtual call. Inside process(), charge(...) compiles to invokevirtual PaymentProcessor.charge. The JVM looks up the slot in the receiver's vtable, so calling through an abstract type costs the same as calling through a concrete one.

If a subclass was compiled against an older parent that didn't yet declare charge(), its vtable slot stays empty. Calling it then throws AbstractMethodError at runtime, a classic symptom of mixing library versions on the classpath.

The next figure shows the vtable picture: the abstract slot is empty in the parent and filled in each concrete subclass.

Fig. 3

Vtables for PaymentProcessor, CardProcessor and UpiProcessor: the validate slot points to the parent's code in all three, while the charge slot is empty in the abstract parent and points to each subclass's own charge in the concrete classes

Vtables for PaymentProcessor, CardProcessor and UpiProcessor: the validate slot points to the parent's code in all three, while the charge slot is empty in the abstract parent and points to each subclass's own charge in the concrete classes

One detail in that picture surprises people: process() has no slot at all. HotSpot doesn't give a vtable entry to a final method that overrides nothing, because there's only ever one target, so the call binds directly.

03Real world: partial implementations in the JDK

The JDK is full of abstract classes doing exactly one job: give you 90% of a contract so you write the last 10%. My favourite example is AbstractList. Implement get(int) and size(), and you get iterator(), contains(), indexOf(), equals(), hashCode(), toString() and subList() for free.

List<Integer> slotIds = new AbstractList<>() {
    @Override public Integer get(int i) { return i * 10; }
    @Override public int size() { return 3; }
};

System.out.println(slotIds);               // prints: [0, 10, 20]
System.out.println(slotIds.indexOf(20));   // prints: 2
slotIds.add(30);                           // throws UnsupportedOperationException

Two details are worth noticing. First, that new AbstractList<>() { ... } is not instantiating an abstract class: it declares an anonymous concrete subclass and instantiates that (the compiler names it something like Demo$1). Second, add() throws because AbstractList gives it a default body that refuses. The parent chose "unmodifiable unless you override", which is a design decision you'll also make in your own abstract classes.

The same shape appears all over: InputStream makes you implement the single-byte read() and builds read(byte[]) and transferTo() on top of it; HttpServlet.service() dispatches to doGet() and doPost(); Spring's AbstractController-style base classes and JUnit's older TestCase worked the same way.

In the real world

The production risk with abstract base classes is the fragile base class. I've seen a shared AbstractRepository in a monorepo grow to 40 protected methods and three layers of subclasses. Someone changed save() to call validate() internally, and a subclass that already called validate() inside its own save() override started validating twice, which doubled the database lookups behind that validation. The fix was to make the template method final, shrink the protected surface to two documented hooks, and move shared helpers into a separate class the subclasses use by composition.

04Advanced: the rules interviewers probe

Illegal modifier combinations

abstract means "a subclass will supply this". Any modifier that prevents a subclass from supplying it contradicts it, and javac rejects the pair with illegal combination of modifiers.

abstract final class Discount { }              // ❌ illegal combination of modifiers: abstract and final

abstract class Repository<T> {
    abstract static Repository<?> empty();     // ❌ illegal combination: abstract and static
    private abstract void connect();           // ❌ illegal combination: abstract and private
    abstract synchronized void flush();        // ❌ illegal combination: abstract and synchronized
    abstract void save(T item) { }             // ❌ abstract methods cannot have a body

    static int defaultBatchSize() { return 500; }   // ✅ concrete static method
    final void saveAll(List<T> items) {             // ✅ concrete final method
        items.forEach(this::save);
    }
}
Combination Legal? Why
abstract class with zero abstract methods ✅ Blocks direct instantiation; still useful as a base
non-abstract class with an abstract method ❌ X is not abstract and does not override abstract method m()
abstract final class or method ❌ final forbids the subclass or override that abstract requires
abstract static method ❌ static methods are hidden, not overridden, so the blank could never be filled
abstract private method ❌ private methods aren't inherited, so no subclass can implement it
abstract synchronized / native method ❌ both describe an implementation, and there isn't one
abstract class with static, final, private methods ✅ they're ordinary concrete members
abstract class implementing an interface without its methods ✅ the obligation passes down to the first concrete subclass

Two more facts finish the picture. An abstract class can extend a concrete class, and it can even re-abstract a concrete method by redeclaring it abstract, forcing its own subclasses to provide a fresh implementation. And a sealed abstract class (Java 17) lets you control exactly which subclasses exist, which is how you model closed sets of variants.

Where candidates slip

Many candidates say "an abstract class must have at least one abstract method" or "an abstract class can't have a constructor". Both are wrong. abstract class Shape {} compiles, and java.util.AbstractCollection has a constructor. When you hear yourself say "abstract classes can't...", check whether you mean "can't be instantiated", which is the only thing they really can't do.

Abstract class vs interface, after Java 8

Before Java 8 the answer was easy: interfaces had only abstract methods and constants. Java 8 added default and static interface methods, and Java 9 added private ones, so "interfaces can't have code" is no longer true. The difference that remains is about state and identity.

Abstract class Interface
Instance fields ✅ any ❌ only public static final constants
Constructors ✅ run in the super(...) chain ❌ none
Concrete methods ✅ any access level default (public), static, private (Java 9)
final / protected methods ✅ ❌
How many a class can extend one many
Can override equals/hashCode/toString ✅ ❌ (a default method can't override Object methods)
Call instruction invokevirtual (vtable) invokeinterface (itable)
Evolving it adding a concrete method is safe adding a default method is binary compatible but can conflict

My default comes down to two questions. Do the types share mutable state or construction rules that must be enforced in one place? Use an abstract class. Is this a capability that unrelated classes might have, alongside others (Comparable, AutoCloseable, Serializable)? Use an interface. In practice the strongest design is often both: a small interface as the public type, plus an optional abstract "skeletal implementation" that does the heavy lifting. That's exactly the JDK's List plus AbstractList, and it's Effective Java's advice.

Performance angle

Abstract methods don't make calls slower. HotSpot's JIT uses class hierarchy analysis: if only one loaded class implements charge(), the call is devirtualised and often inlined, costing nothing. At a call site that sees two receiver types, the JIT emits a bimorphic inline cache (two type checks, then inlined bodies). Only at three or more types does it fall back to a real vtable load, roughly a few nanoseconds per call. The abstract keyword itself has no runtime cost.

Where abstract doesn't apply

You can't put abstract on fields, constructors or local variables, and an enum can't be declared abstract. An enum can, however, declare an abstract method if every constant supplies a body, which is a compact strategy pattern covered in A guide to Java enums. Records are implicitly final, so a record can't be abstract either.

05How to say it in the interview

When I ask about abstract classes, I'm listening for three things. Do you know an abstract class can have state and a constructor, and can you explain why (the subclass object contains the parent's fields)? Can you name the template method pattern and show where final belongs? And when I ask "abstract class or interface?", do you answer with a reason about state and multiple inheritance instead of a pre-Java 8 list?

In the interview

"An abstract class is a partially implemented type. It can't be instantiated, but it can have fields, constructors, concrete methods and abstract methods with no body. The constructor exists because every subclass object contains the parent's fields, so it runs in the super chain and enforces the parent's invariants. The typical use is the template method pattern: a final method defines the steps and calls abstract hooks, like AbstractList building indexOf and iterator on top of get and size. abstract can't go with final, static or private, because each of those prevents overriding. I'd avoid calling abstract methods from the constructor, since the subclass fields aren't initialised yet. I pick an abstract class when types share state or construction logic, and an interface when it's a capability unrelated classes can mix in; often I'd offer both, an interface plus a skeletal abstract class."

On the whiteboard

abstract class = can't `new`, can have fields + ctor + concrete + abstract methods
abstract method = no body, ACC_ABSTRACT, no Code attr, empty vtable slot
new Sub() → Object() → Abstract() → Sub fields → Sub() body
ctor calling abstract method → sees subclass fields as null/0
template method: public final run() { step1(); hook(); step2(); }
illegal: abstract + final | static | private | synchronized | native
abstract class: state, ctor, protected/final methods, single parent
interface: capability, many, default/static/private, no state
best of both: interface + skeletal AbstractX (List + AbstractList)
Practise this topicAbstract classes & interfaces: interview questions & scenarios
Further reading (7)