Abstract Classes in Java

Learn about partially implemented classes that define contracts for subclasses using abstract methods and concrete implementations.

published: reading time: 16 min read author: Geek Workbench

Abstract Classes in Java

Abstract classes sit between interfaces and concrete classes — they provide a common implementation that subclasses inherit, while leaving certain methods undefined for subclasses to implement.

Introduction

Abstract classes sit between pure interfaces and fully-implemented concrete classes. They exist to solve a specific problem: you have related classes that share implementation code, but also require certain methods to be customized by each subclass. A plain interface cannot hold shared implementation, and a concrete class cannot leave methods undefined. Abstract classes bridge this gap by letting you declare abstract methods that subclasses must implement while providing concrete methods that subclasses inherit ready-made.

This matters in practice because inheritance hierarchies built on abstract classes tend to be more maintainable than those built on concrete parent classes. The Template Method pattern is the canonical use case — a final method in the abstract class defines an algorithm’s skeleton, calling abstract methods at key steps. Subclasses provide the implementations of those steps, and the algorithm structure is guaranteed across every subclass. Without this pattern, you’d either duplicate the algorithm in every subclass or use fragile inheritance chains that break when parent implementation changes.

This post covers when to use abstract classes versus interfaces, the Template Method pattern in detail, constructor behavior (including why you should never call abstract methods from constructors), sealed classes for controlled inheritance, and the failure modes that catch most developers — including the infamous NPE from calling overridable methods during construction. By the end, you’ll know exactly when abstract classes earn their place in your design versus when a plain interface or composition would serve better.

When to Use

Use abstract classes when:

  • Shared implementation exists — multiple related subclasses share common code
  • A common base type is needed — but some behavior cannot be fully specified
  • You need single inheritance — one class can extend only one abstract class
  • You want to provide a template — subclasses fill in the blanks following a defined pattern
public abstract class Notification {
    // Concrete method — shared implementation
    protected final void log(String message) {
        System.out.println("[NOTIFICATION] " + message);
    }

    // Abstract method — must be implemented by subclasses
    public abstract void send(String recipient, String content);

    // Concrete method using abstract method
    public void notifyUser(String recipient, String content) {
        log("Sending to " + recipient);
        send(recipient, content);  // Calls subclass implementation
        log("Sent successfully");
    }
}

public class EmailNotification extends Notification {
    @Override
    public void send(String recipient, String content) {
        System.out.println("EMAIL to " + recipient + ": " + content);
    }
}

public class SmsNotification extends Notification {
    @Override
    public void send(String recipient, String content) {
        System.out.println("SMS to " + recipient + ": " + content);
    }
}

When Not to Use

Avoid abstract classes when:

  • Multiple unrelated classes need the same contract — use interfaces
  • All methods should be abstract — interfaces express pure contracts better
  • You need multiple inheritance — a class can only extend one abstract class
  • Simplicity is preferred — if only one subclass exists, maybe a regular class is better
// Don't: abstract class for a single implementation
public abstract class SingletonBase {
    protected void commonMethod() { }
}

public class OnlyUse extends SingletonBase { }  // Just use a regular class

// Do: abstract class for shared behavior + template
public abstract class Parser {
    // Common structure all parsers follow
    public final ParseResult parse(String input) {
        validate(input);
        return doParse(input);
    }

    private void validate(String input) {
        if (input == null || input.isEmpty()) {
            throw new IllegalArgumentException("Input cannot be empty");
        }
    }

    // Subclasses implement this
    protected abstract ParseResult doParse(String input);
}

Abstract Class Architecture — Mermaid Diagram

classDiagram
    class Notification {
        +log(message) void
        +send(recipient, content) void$abstract
        +notifyUser(recipient, content) void
    }
    class EmailNotification {
        +send(recipient, content) void
    }
    class SmsNotification {
        +send(recipient, content) void
    }
    class PushNotification {
        +send(recipient, content) void
    }
    Notification <|-- EmailNotification
    Notification <|-- SmsNotification
    Notification <|-- PushNotification
    note for Notification "abstract — cannot be instantiated\nsend() is abstract — no implementation"

Failure Scenarios

1. Trying to Instantiate an Abstract Class

public abstract class Shape {
    public abstract double area();
}

// Does not compile
Shape s = new Shape();  // Error: Cannot instantiate abstract class

// Correct usage
Shape s = new Circle(5);  // Shape reference, Circle object

2. Forgetting to Implement Abstract Methods

public abstract class Base {
    public abstract void doSomething();
}

// Does not compile — must implement all abstract methods
public class Incomplete extends Base {
    // Missing @Override doSomething()
}

// Fix: implement all abstract methods or declare class abstract
public class Complete extends Base {
    @Override
    public void doSomething() {
        System.out.println("Done!");
    }
}

3. Calling Abstract Methods from Constructor

public abstract class Base {
    public Base() {
        // Don't call overridable methods here
        initialize();  // Will call subclass override, but object not fully constructed
    }

    public abstract void initialize();
}

public class Derived extends Base {
    private final int value;

    public Derived() {
        this.value = 10;  // This happens AFTER initialize() called in Base constructor
    }

    @Override
    public void initialize() {
        // At this point, value is still default 0, not yet 10!
        System.out.println("Initializing with value: " + value);  // Prints 0!
    }
}

Trade-off Table

FeatureAbstract ClassInterface
ImplementationCan have fully implemented methodsDefault methods (Java 8+) but still limited
FieldsCan have any fieldsStatic constants only
Multiple inheritanceSingle class extends one abstractClass can implement many interfaces
ConstructorsCan have constructorsCannot have constructors
Access modifiersAll access levelsMethods are implicitly public

Code Snippets

Template Method Pattern

public abstract class DataProcessor {
    // Template method — defines the algorithm skeleton
    public final void process(String dataSource) {
        String rawData = fetchData(dataSource);
        String cleanedData = clean(rawData);
        Object result = analyze(cleanedData);
        save(result);
    }

    // Steps to be implemented by subclasses
    protected abstract String fetchData(String source);
    protected abstract Object analyze(String data);
    protected abstract void save(Object result);

    // Shared implementation — can be used as-is or overridden
    protected String clean(String raw) {
        return raw.trim().toLowerCase();
    }
}

public class CloudDataProcessor extends DataProcessor {
    @Override
    protected String fetchData(String source) {
        return cloudClient.download(source);
    }

    @Override
    protected Object analyze(String data) {
        return new MLModel(data).predict();
    }

    @Override
    protected void save(Object result) {
        database.store(result);
    }
}

Abstract Class with Protected Helper

public abstract class Queue<T> {
    // Abstract methods — subclasses implement storage
    protected abstract void enqueueInternal(T item);
    protected abstract T dequeueInternal();
    protected abstract boolean isEmptyInternal();
    protected abstract int sizeInternal();

    // Concrete methods using abstract primitives
    public void enqueue(T item) {
        if (item == null) {
            throw new IllegalArgumentException("Cannot enqueue null");
        }
        enqueueInternal(item);
    }

    public T dequeue() {
        if (isEmpty()) {
            throw new IllegalStateException("Queue is empty");
        }
        return dequeueInternal();
    }

    public boolean isEmpty() {
        return isEmptyInternal();
    }

    public int size() {
        return sizeInternal();
    }
}

Observability Checklist

  • At least one abstract method makes class worth being abstract
  • Abstract class has shared implementation (otherwise use interface)
  • All abstract methods implemented by concrete subclasses
  • Abstract methods are public (implicit in abstract class)
  • Constructors don’t call overridable methods

Security Notes

  • Sealed abstract classes (Java 17+) — restrict which classes can extend your abstract class
  • Don’t call abstract methods in constructors — subclass state not yet initialized
  • Final concrete methods — methods that shouldn’t be overridden should be final
  • Protected fields — if any, document their invariants clearly
// Sealed abstract class — control who can extend (Java 17+)
public abstract sealed class Payment permits CreditCardPayment, DebitPayment, WireTransferPayment {
    protected final double amount;

    protected Payment(double amount) {
        if (amount <= 0) {
            throw new IllegalArgumentException("Amount must be positive");
        }
        this.amount = amount;
    }

    public abstract void execute();
}

public final class CreditCardPayment extends Payment { }
public final class DebitPayment extends Payment { }
public final class WireTransferPayment extends Payment { }

// No other class can extend Payment — compiler enforces this

Pitfalls

  1. Forgetting abstract keyword on methods — methods without implementation in abstract class must be abstract
  2. Calling abstract methods from constructor — subclass not yet constructed
  3. Too many abstract methods — if many, consider splitting into multiple abstract classes or interfaces
  4. Abstract class for only one concrete subclass — maybe just merge into the concrete class
  5. Deep hierarchies — abstract classes can create tight coupling; prefer composition
// Bad: abstract class with no real implementation — use interface
public abstract class Serializable {
    void serialize();  // Could just be an interface
}

// Good: abstract class with shared implementation
public abstract class LoggingBean {
    protected Logger logger = Logger.getLogger(getClass());

    protected void logInfo(String msg) { logger.info(msg); }
    protected void logError(String msg, Exception e) { logger.error(msg, e); }

    // Subclasses implement business logic
    public abstract void doSomething();
}

Quick Recap

  • abstract class = cannot be instantiated, serves as base for subclasses
  • abstract method = no implementation, must be overridden by concrete subclasses
  • Concrete subclass = implements all abstract methods, can be instantiated
  • Template method = final method in abstract class that calls abstract methods (subclass controls algorithm)
  • Cannot instantiate directly — only its concrete subclasses can be created

Interview Questions

1. What is an abstract class in Java?

Model Answer: "An abstract class is a class marked with the `abstract` keyword that cannot be instantiated directly. It serves as a base type for subclasses and may contain abstract methods (methods without implementation) that subclasses must implement, concrete methods (with implementation) that are shared across subclasses, or both."

2. What is the difference between an abstract class and an interface?

Model Answer: "An abstract class can have both abstract and concrete methods, any type of fields, and constructors. An interface (pre-Java 8) could only have abstract methods and static constants. Java 8+ allows default methods but still cannot have instance fields or constructors. A class can implement multiple interfaces but can only extend one abstract class."

3. Can an abstract class have a constructor?

Model Answer: "Yes, abstract classes can have constructors. Even though you cannot instantiate an abstract class directly, subclasses must call `super()` in their constructor to ensure parent initialization runs. Constructors in abstract classes are typically used to initialize shared fields and establish invariants that all subclasses depend on."

4. Can an abstract class be sealed in Java?

Model Answer: "Yes, Java 17+ allows `sealed` abstract classes. Combining `abstract sealed` with `permits` explicitly lists which classes may extend the abstract class. This provides controlled inheritance for security-sensitive hierarchies where you want to prevent unknown subclasses from being created."

5. What is the Template Method pattern and how does it relate to abstract classes?

Model Answer: "The Template Method pattern uses a `final` method in an abstract class to define an algorithm's skeleton, calling abstract methods at key steps. Subclasses provide implementations of those abstract steps, controlling the algorithm's specific behaviors without changing its structure. This enforces consistency across all subclasses while allowing customization."

6. What happens if a subclass doesn't implement all abstract methods?

Model Answer: "The code does not compile. Any subclass of an abstract class must implement all abstract methods from the parent, or the subclass itself must be declared abstract. This is enforced by the compiler — you cannot create a concrete class with unimplemented abstract methods."

7. Why should abstract methods not be called from constructors?

Model Answer: "When a subclass constructor runs, the parent constructor executes first. If the parent constructor calls an abstract method, the subclass override executes before the subclass's own fields are initialized. This means the abstract method runs on a partially constructed object with uninitialized fields."

8. What is the difference between abstract class and concrete class?

Model Answer: "A concrete class can be instantiated directly with `new`. An abstract class cannot be instantiated — it exists only to be extended. Concrete classes must implement all inherited abstract methods; abstract classes may leave methods unimplemented for subclasses to fill in."

9. Can an abstract class extend a concrete class?

Model Answer: "Yes, an abstract class can extend a concrete class. The abstract class inherits all the concrete class's implementation and can add its own abstract methods on top. This is uncommon but valid — the abstract class is adding a layer of abstraction on top of existing functionality."

10. When should you prefer an abstract class over an interface?

Model Answer: "Prefer an abstract class when you have genuine shared implementation that subclasses need to inherit — reusable code, not just a contract. When you need constructors or instance fields. When you want single inheritance with multiple interface implementations. When the relationship is genuinely an 'is-a' relationship with shared behavior."

11. Can an abstract class implement an interface?

Model Answer: "Yes, an abstract class can implement an interface and need not implement all of its methods. The abstract class can leave some interface methods unimplemented, and concrete subclasses of the abstract class must implement them. This is a common pattern for providing partial implementation of a contract."

12. What is a concrete subclass?

Model Answer: "A concrete subclass is a class that extends an abstract class and implements all of its abstract methods. Unlike an abstract class, a concrete subclass can be instantiated with `new`. All abstract methods from the parent hierarchy must be implemented for a class to be concrete."

13. Can abstract methods be private in Java?

Model Answer: "No, abstract methods cannot be private. If an abstract method were private, subclasses could not override it, which defeats the purpose of being abstract. The abstract method must be accessible to any class that might extend the abstract class and provide an implementation."

14. What is the relationship between abstract classes and the fragile base class problem?

Model Answer: "Abstract classes suffer from the same fragile base class problem as regular classes — changes to the abstract parent can break subclasses. However, because abstract classes typically have more undefined behavior (abstract methods), subclasses have more flexibility in how they implement behavior. Still, changing concrete methods in the abstract parent can unexpectedly affect subclasses."

15. Can you declare a class both abstract and final?

Model Answer: "No, this is a compile error. A class declared `abstract` cannot be instantiated, by definition — something must extend it. A class declared `final` cannot be extended. These modifiers are mutually exclusive. The compiler will reject the combination."

16. How do abstract classes support polymorphism?

Model Answer: "You can declare a variable of the abstract type and assign any concrete subclass instance to it. Method calls on the abstract reference will resolve to the concrete implementation at runtime. This is polymorphism — the calling code works with the abstract interface without knowing which concrete subclass is actually being used."

17. What is the difference between abstract class and interface for multiple inheritance?

Model Answer: "A class can implement multiple interfaces but can only extend one abstract class. This is the key advantage of interfaces for achieving multiple inheritance-like behavior. If you need both shared implementation and multiple contracts, you can extend one abstract class and implement multiple interfaces."

18. Can abstract classes have static methods?

Model Answer: "Yes, abstract classes can have static methods. Static methods belong to the class, not to instances, so they can be called without an instance. They are not inherited as abstract methods would be, but they are accessible to all subclasses and can be called normally."

19. What is the difference between the Template Method pattern and the Strategy pattern?

Model Answer: "Template Method uses inheritance — the abstract class defines the algorithm skeleton and subclasses provide step implementations. Strategy uses composition — the context class holds an interface reference and clients inject different strategy implementations at runtime. Template Method locks in the algorithm structure; Strategy allows swapping the entire algorithm."

20. How do sealed abstract classes improve security?

Model Answer: "Sealed abstract classes with `permits` explicitly list which classes may extend them. The compiler enforces that no other class can extend a sealed abstract class. This prevents malicious or unexpected subclasses from being introduced into the hierarchy, which is important for security-sensitive code that relies on specific subclasses behaving correctly."

Further Reading

Conclusion

Abstract classes occupy a middle ground between interfaces (pure contracts, no implementation) and concrete classes (fully implemented). They exist to provide shared implementation that subclasses inherit, while also defining abstract methods that each subclass must implement. This makes abstract classes ideal for situations where you have genuine code reuse across related classes, not just a shared contract.

The Template Method pattern is the canonical use case for abstract classes. A final method in the abstract class defines an algorithm’s skeleton, calling abstract methods at key points. Subclasses provide the specific implementations of those steps while being forced to follow the overall structure. This ensures consistent behavior across all subclasses without duplicating the algorithm itself.

Constructors in abstract classes work differently than in concrete classes — you cannot instantiate an abstract class directly, but subclasses must still call super() to ensure the parent initialization runs. The security checklist point about not calling overridable methods from constructors is critical: when a subclass constructor runs, the parent constructor runs first, and if that parent constructor calls an abstract method, the subclass override executes on a partially initialized object.

The sealed class feature (Java 17+) adds control over who can extend an abstract class. Using sealed with permits explicitly lists which classes may inherit, preventing unexpected subclasses and potential security issues. This is particularly useful for abstract classes that form a controlled hierarchy, like the payment types example in the security notes.

Abstract classes connect directly to polymorphism (covered in Polymorphism in Java) — you can store any concrete subclass in a variable of the abstract type, and the correct overridden methods will be called at runtime. They also relate to interfaces (detailed in Interfaces in Java), which a class can implement many of while extending only one abstract class, giving you both multiple contracts and shared implementation in the same type hierarchy.

Category

Related Posts

Arithmetic Operators in Java

Master Java arithmetic operators: addition, subtraction, multiplication, division, and modulo with integer division gotchas and operator precedence explained.

#java-arithmetic-operators #java #java-fundamentals

Array Basics in Java

Learn Java array fundamentals: declaration, initialization, element access, and the length property explained simply.

#java-array-basics #java #java-fundamentals

ArrayList in Java

Learn ArrayList: dynamic resizing, internal array management, when to choose ArrayList over plain arrays, and performance trade-offs.

#java-arraylist #java #java-fundamentals