AlgoInsight

Free lesson · Java Basic To Advance

Introduction to Abstract Classes

Medium7 min readjava

The Partial Implementation

Module 9 covered interfaces — pure contracts that define what methods must exist but provide no implementation (with the modern exception of default methods). This Module covers abstract classes — classes that combine implementation with required customization. They're the middle ground between fully concrete classes and pure interfaces.

An abstract class typically provides:

Subclasses inherit the complete methods, implement the abstract ones, and get the shared state for free. This is the partial implementation pattern — half a class, with subclasses completing it.

Brandon Carter continuing through Module 10. Abstract classes are one of the most-debated OOP topics. Some teams use them heavily, others almost never. Knowing when each approach fits is senior-level Java design.

What an Abstract Class Is

An abstract class is declared with the abstract keyword:

public abstract class Animal {
    private String name;

    public Animal(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public abstract void speak();
}

This class:

The key restriction: you cannot instantiate an abstract class directly:

Animal a = new Animal("Rex");   // compile error — Animal is abstract

You can only instantiate concrete subclasses:

public class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }

    @Override
    public void speak() {
        System.out.println(getName() + " says Woof!");
    }
}

Dog rex = new Dog("Rex");
rex.speak();   // "Rex says Woof!"

Dog inherits getName() and the name field, must provide speak(). The combination produces a complete, concrete class.

Why Abstract Classes Exist

Three primary reasons:

1. Share implementation across related classes.

If multiple classes have common state and behavior, an abstract parent factors that out:

public abstract class HttpHandler {
    private final String basePath;
    private final Logger logger;
    private long requestCount = 0;

    protected HttpHandler(String basePath, Logger logger) {
        this.basePath = basePath;
        this.logger = logger;
    }

    public final HttpResponse handle(HttpRequest request) {
        requestCount++;
        logger.info("Handling " + basePath + " request");
        try {
            return doHandle(request);
        } catch (Exception e) {
            logger.error("Handler failed", e);
            return HttpResponse.serverError();
        }
    }

    protected abstract HttpResponse doHandle(HttpRequest request);

    public long getRequestCount() {
        return requestCount;
    }
}

Every HttpHandler subclass gets:

Subclasses only have to provide doHandle() — the actual handler logic. The framework provides everything else. This factor of "common scaffolding + customized core" is the abstract class's main use case.

2. Force subclasses to implement specific methods.

Abstract methods are the mechanism for "you MUST provide this." A regular concrete class can be extended without overriding anything. An abstract class with abstract methods forces subclasses to implement them.

This makes the contract explicit and compiler-enforced. You can't extend an abstract class and "forget" to implement a required method — the compiler catches it.

3. Provide a partially-completed type that callers can use.

Callers can work with the abstract class type even if they have a concrete subclass:

public void process(Animal animal) {
    System.out.println("Processing " + animal.getName());
    animal.speak();
}

process(new Dog("Rex"));   // works through polymorphism
process(new Cat("Whiskers"));

The process method accepts any Animal subclass. It knows getName() exists (concrete in Animal) and that speak() will work (abstract, but every concrete subclass must implement it).

What Distinguishes Abstract from Concrete Classes

The differences are mechanical:

Property Concrete Class Abstract Class
abstract keyword on class No Yes
Can be instantiated Yes No
Can have abstract methods No Yes
Can have concrete methods Yes Yes
Can have constructors Yes Yes (called by subclasses)
Can have instance fields Yes Yes
Can be extended Yes (unless final) Yes

The only "different" features of abstract classes: they can't be instantiated directly, and they can have abstract methods. Otherwise they behave like regular classes — they have state, constructors, methods, and can be extended.

A Real Production Pattern

At AWS S3, our operation framework has an abstract S3Operation base class:

public abstract class S3Operation<Req, Resp> {
    protected final String region;
    protected final S3Client client;
    private final MetricsCollector metrics;
    private final RetryStrategy retryStrategy;

    protected S3Operation(String region, S3Client client,
                         MetricsCollector metrics, RetryStrategy retry) {
        this.region = region;
        this.client = client;
        this.metrics = metrics;
        this.retryStrategy = retry;
    }

    public final Resp execute(Req request) {
        long start = System.nanoTime();
        try {
            validateRequest(request);
            Resp response = executeInternal(request);
            metrics.recordSuccess(getOperationName(), System.nanoTime() - start);
            return response;
        } catch (Exception e) {
            metrics.recordFailure(getOperationName(), e);
            throw e;
        }
    }

    protected abstract String getOperationName();
    protected abstract void validateRequest(Req request);
    protected abstract Resp executeInternal(Req request);
}

public class GetObjectOperation extends S3Operation<GetRequest, GetResponse> {
    public GetObjectOperation(/* ... */) { super(/* ... */); }

    @Override protected String getOperationName() { return "GetObject"; }

    @Override protected void validateRequest(GetRequest r) {
        if (r.getBucket() == null) throw new IllegalArgumentException();
    }

    @Override protected GetResponse executeInternal(GetRequest r) {
        return client.getObject(r);
    }
}

Every S3 operation subclass gets retry, metrics, validation orchestration, and error handling for free. Subclasses focus on their specific logic. There are over 50 operation types in our codebase — without the abstract base, every one would need to duplicate the scaffolding.

This is the abstract class pattern at scale: significant shared implementation + abstract hooks for customization.

When NOT to Use Abstract Classes

Abstract classes have costs:

Single-inheritance constraint. A class extending your abstract class can't extend anything else. If subclasses might need to participate in other hierarchies, abstract classes lock them in.

Inflexibility. Adding abstract methods later breaks all subclasses. Even adding concrete methods might collide with subclass methods.

Implementation coupling. Subclasses inherit your implementation. Changes to the parent ripple through. This is the "fragile base class" problem from Topic 8.12.

Hard to test in isolation. Abstract classes can't be instantiated for testing. You have to test through concrete subclasses, often creating test-only subclasses just for testing.

If these costs outweigh the benefits, prefer interfaces with default methods, or use composition.

When Abstract Classes Fit Best

Abstract classes are the right choice when:

graph TD
    A["Need an abstraction?"] --> B{"Significant<br/>shared state?"}

    B -->|Yes| C["Abstract class"]
    B -->|No| D{"Multiple inheritance<br/>needed?"}

    D -->|Yes| E["Interface"]
    D -->|No| F{"Significant<br/>shared implementation?"}

    F -->|Yes - too much for defaults| C
    F -->|No - simple contract| E

    style C fill:#3b82f6,color:#fff
    style E fill:#10b981,color:#fff
This diagram shows the high-level decision between abstract classes and interfaces. Start with whether you need shared instance state — fields that multiple subclasses need to inherit. If yes, abstract class is the right choice; interfaces can't have instance state. If you don't need shared state but you do need a class to participate in multiple type relationships, use interfaces — a class can implement many interfaces but extend only one abstract class. If neither shared state nor multiple inheritance matters, the decision comes down to how much shared implementation you have. Lots of shared logic that's awkward as default methods favors abstract class. Simple contracts favor interfaces. Module 9 Topic 12 covered this comparison in depth.

What This Module Covers

Module 10 covers abstract classes in depth:

After this module, you'll be able to design abstract class hierarchies, choose between abstract classes and interfaces, recognize the template method pattern, and combine both abstraction mechanisms via skeletal implementations.

Common Pitfalls

Warning: Trying to instantiate an abstract class. Compile error. You can only new concrete subclasses.

Forgetting that abstract classes can have constructors. They can — the constructor runs as part of the subclass's construction chain.

Adding too many abstract methods. Each one is a burden on every subclass. Keep abstract methods focused.

Choosing abstract class when interface would do. Single-inheritance limits subclasses. Prefer interfaces when in doubt (Effective Java Item 20).

Premature abstraction. Don't extract an abstract class until you have multiple concrete classes sharing actual structure. YAGNI applies.

Why This Matters in Interviews

Interview: Abstract class questions appear in nearly every Java interview. "What's an abstract class?" "When would you use one?" "What's the difference between abstract class and interface?" (revisiting Topic 9.12).

Topic 10.12 covers the full Interview Lens.

Try It

Exercise: Write each:

  1. An abstract class Shape with concrete getName() and abstract area(). Two concrete subclasses (Circle, Square).
  2. Try to instantiate Shape directly — observe the compile error.
  3. Instantiate the subclasses, treat them as Shape references, call methods.
  4. Add a constructor to Shape. Verify subclasses must call super().
  5. Try writing the same abstraction as an interface with a default method for getName(). Compare the two approaches.

Each exercise builds basic abstract class intuition.

Back to Java Basic To Advance