Topic 64 of 119 6. Object-Oriented Programming in Java Interactive Tutorial

Abstract Classes in Java: Incomplete Types and Partial Implementation

16 mins read Last updated: 22 Aug 2026 26,072 views

Direct Answer & Core Concept

An abstract class is a class you are not allowed to instantiate but are very much allowed to inherit from. Inside a single class you can mix fully working code with methods that have a signature and no body at all, and the compiler guarantees that no object ever exists until every one of those gaps has been filled.
Main.java
Run Online
abstract class Notification {
    protected final String recipient;
    protected Notification(String recipient) {
        this.recipient = recipient;
    } void log() {
        // concrete, works immediately
        System.out.println("to " + recipient);
    } abstract void send();
    // no body, a subclass must supply it
}
Main.java
Run Online
new Notification();
// error: Notification is abstract; cannot be instantiated
Notification n = new EmailNotification("ada@example.com", "smtp.local");
// fine

That mix is the entire point, and it is what people mean by partial implementation. The parent holds whatever is genuinely identical across the family, and each child finishes whatever actually varies. In the snippet above, recipient and log() are shared; send() is left open, so an email subclass fills it in one way and an SMS subclass fills it in another.

The payoff shows up at the call site. One array holds every subtype, one loop processes them, and no line of that loop needs an instanceof test or a cast, because runtime dispatch already picks the right implementation from the actual object.

Three rules cover most confusion. A concrete subclass must implement every inherited abstract method or declare itself abstract, and that obligation passes down the chain until a concrete leaf closes it. An abstract method can never be static, private, or final, since javac rejects those combinations. And an abstract class is allowed to declare zero abstract methods, where the keyword alone serves to block direct construction.

Choose an interface to say what a thing can do, and an abstract class to say what a thing is and how the shared part works. The deciding question is whether the subclasses need shared state or shared constructor logic, because interfaces hold no per-object state and declare no constructors.

Detailed Explanation & Mechanics

1) What "Incomplete" Actually Means

An abstract class is a class you are not allowed to instantiate but are very much allowed to inherit from. That is the whole rule, and it sounds arbitrary until you notice what it prevents: an object whose behaviour has a hole in it.

Inside one abstract class you can freely mix complete code and bare declarations. Fields work. Constructors run. Concrete methods execute exactly as written. Alongside all of that you may declare methods with a name, a return type, and parameters but no body at all. Those are abstract methods, and each one is a promise the class makes and refuses to keep itself.

Main.java
Run Online
abstract class Notification {
    protected final String recipient;
    // state, fully usable
    protected Notification(String recipient) {
        this.recipient = recipient;
        // constructor, runs via super(...)
    } void log() {
        // concrete, works today
        System.out.println("to " + recipient);
    } abstract void send();
    // signature only, no body
}

So the type is not empty, it is unfinished. Enough of it is missing that new Notification() would hand you an object that cannot do its own job, so the compiler refuses:

Main.java
Run Online
Notification n = new Notification();
// error: Notification is abstract; cannot be instantiated

Declaring a reference of that type is completely fine. Only construction is blocked, which is why Notification n = new EmailNotification(...) compiles without complaint.

2) Partial Implementation in Practice

Partial implementation is the reason abstract classes exist, and the term is worth taking literally. Some of the type is implemented; the rest is left open on purpose. The parent owns whatever is genuinely identical across the family, and the children own whatever actually varies.

In the example above, recipient and log() are the same for every channel, so they are written once in the parent. send() is not the same for an email than it is for an SMS, so it stays abstract. Each child fills in its own version:

Main.java
Run Online
final class EmailNotification extends Notification {
    private final String smtpServer;
    EmailNotification(String to, String smtpServer) {
        super(to);
        // the parent constructor still runs
        this.smtpServer = smtpServer;
    } @Override void send() {
        System.out.println("connecting to " + smtpServer);
    }
}

The payoff appears at the call site. One array holds both kinds, one loop processes them, and not one line of that loop knows which type it is holding:

Main.java
Run Online
for (Notification n : queue) {
    n.log();
    n.send();
}

Without the shared parent you would duplicate the recipient field and the logging in every class, and the loop would need an instanceof test. The abstract class moves that decision to compile time, where it costs nothing at run time.

A common misreading is that abstract means unimplemented. It does not. The keyword blocks construction and permits body-less declarations. Everything concrete in the class is fully live.

3) Abstract Method Against Concrete Method

PropertyAbstract methodConcrete method
BodyNone, not even an empty {}Required
Where it may be declaredAbstract class or interface onlyAnywhere
Modifiers allowedNo static, private, or finalAny
Who decides the behaviourThe subclass that implements itThe author, once
Left unimplemented in a childCompile error unless the child is abstractNo error at all

One row deserves emphasis. A method cannot be both abstract and static, and javac says so plainly:

Main.java
Run Online
abstract class A {
    abstract static void m();
}// error: illegal combination of modifiers: abstract and static

The same error appears for private abstract and final abstract. The reasoning is consistent: a static method belongs to the class rather than to instances, a private one is not inherited, and a final one is locked. An abstract method has to be something an object can actually do and something a child can actually replace.

4) The Chain Rule: Abstractness Flows Downward

If a subclass does not implement every inherited abstract method, it must declare itself abstract, and the obligation passes on to its children. It is a to-do list that shrinks as you descend, and nothing is allowed off the hook until some concrete class clears it.

Main.java
Run Online
abstract class Shape {
    abstract double area();
}abstract class Polygon extends Shape {
    // area() still open
    abstract boolean isConvex();
}final class Rectangle extends Polygon {
    // both closed
    private final double width;
    private final double height;
    Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    } @Override double area() {
        return width * height;
    } @Override boolean isConvex() {
        return true;
    }
}

Try to instantiate the middle class and the compiler stops you again, this time naming the specific method:

Main.java
Run Online
class Middle extends Shape {
}Polygon p = new Middle();
// error: Middle is not abstract and does not override
//        abstract method area() in Shape

Note that Polygon being abstract is not what causes that error. It is that Middle is concrete while leaving a method open.

An abstract class is allowed to declare zero abstract methods. The keyword on its own blocks direct construction, which is occasionally useful for a class that should only ever be reached through a factory. It compiles and behaves exactly as you would expect.

5) The Template Method Pattern


Diagram showing an abstract Notification class split into concrete shared code and abstract methods, with EmailNotification and SmsNotification subclasses filling in the abstract half
What the parent solves and what each child must finish.

The canonical use of an abstract class is to fix a sequence in the base and leave only the varying steps open. Mark the orchestrating method final so a subclass cannot reorder or skip the steps, then declare the steps themselves as abstract.

Main.java
Run Online
abstract class ReportGenerator {
    public final void generate() {
        // the skeleton, locked
        loadData();
        format();
        System.out.println("report complete");
    } protected abstract void loadData();
    // the blanks to fill
    protected abstract void format();
}

Because generate() is final, every subclass runs the same algorithm in the same order, and the only freedom is in the two hooks. This is the strongest argument for choosing an abstract class over an interface: an interface can demand that loadData() exists, but it cannot supply the generate() that calls it, and it cannot stop a subclass from reordering its own steps.

Compare that with an method override that is left empty. An empty body compiles happily and produces a silent no-op at run time:

Main.java
Run Online
class Broken extends ReportGenerator {
    @Override protected void loadData() {
    } // compiles, does nothing
    @Override protected void format() {
    }
}

Declaring the hooks abstract instead turns that silent no-op into a compile error the moment somebody forgets. That is the whole argument for preferring abstract over an empty concrete method.

6) Abstract Class or Interface

PointAbstract classInterface
FieldsAny modifier, including private and staticImplicitly public static final
ConstructorsAllowedNot allowed
Method bodiesAny mix of abstract and concretedefault and static bodies since Java 8
Per-object stateYesNo
Multiple parentsOne class onlyAny number of interfaces
Best suited toSharing an implementation and a contractDeclaring a capability or a role

A workable rule: use an interface to say what a thing can do, and an abstract class to say what a thing is and how the shared part works. The decisive question is whether the subclasses need shared state or shared constructor logic. If they do, no amount of interface cleverness will supply it, because interfaces hold no per-object state and define no constructors.

The two combine freely, and the order matters for super:

Main.java
Run Online
interface Printable {
    void print();
}abstract class Document implements Printable {
    protected int pageCount;
    void printHeader() {
        System.out.println("pages: " + pageCount);
    } // print() is still abstract here, and that is allowed
}

7) Constructors on an Abstract Class

This surprises nearly everyone the first time. An abstract class may declare a constructor, and it is not contradictory. The constructor exists for the subclasses, not for the abstract class itself, and it is reached through super(...).

Main.java
Run Online
abstract class DatabaseConnection {
    protected final String url;
    DatabaseConnection(String url) {
        this.url = url;
    } abstract boolean testConnection();
}final class MySqlConnection extends DatabaseConnection {
    MySqlConnection() {
        super("jdbc:mysql://localhost:3306/app");
    } @Override boolean testConnection() {
        return url.startsWith("jdbc:mysql");
    }
}

The parent constructor always runs before the child body, so shared state is initialised exactly once and every subclass passes through the same validation. What you still cannot do is write new DatabaseConnection(...).

One ordering rule catches people out constantly: super(...) must be the first statement in a constructor, before any field assignment of your own.

Main.java
Run Online
abstract class Loader {
    protected final String path;
    Loader(String path) {
        this.path = path;
    } abstract void open();
}final class Broken extends Loader {
    private final int retries;
    Broken(String path, int retries) {
        this.retries = retries;
        super(path);
    } // error: call to super must be first statement in constructor

Move the super(path) call to the top and the class compiles. Getting this order wrong is the single most common compile error in any class that extends something.

8) Common Misconceptions

ClaimVerdict
abstract means the class is emptyFalse. Fields, constructors, and concrete methods all work normally.
You cannot declare a variable of an abstract typeFalse. Only new is blocked, references are fine.
An abstract class must have at least one abstract methodFalse. Zero is legal and simply blocks construction.
Every subclass of an abstract class is concreteFalse. It stays abstract until every method is implemented.
An abstract method may be staticFalse. Illegal combination of modifiers.
An abstract class cannot have a constructorFalse. It can, and subclasses invoke it via super.
An empty method body works the same as abstractFalse. Empty compiles into a silent no-op.
Abstract classes are slower at run timeFalse. No runtime check exists; the work happens at compile time.

9) When to Use One and When to Skip It

Reach for an abstract class when several subclasses genuinely share data or code and you want that sharing enforced by the compiler rather than by team discipline. The Notification family above is exactly that shape.

Skip it when the only thing in common is a method name. Two unrelated classes that each happen to have a send() do not need a parent, and duplicating three lines is cheaper than an inheritance tree nobody asked for.

Signs you are forcing it:

  • The abstract class has exactly one subclass and no second one is planned.
  • You added a parent only to satisfy a rule of thumb.
  • The parent holds no real state, and only method names are being shared. That is an interface wearing a hat.

A useful test: if you cannot complete the sentence "every subclass needs..." with at least one field or one real method, an interface is the better tool.

Frequently Asked Questions

Can we instantiate an abstract class in Java?


No. new AbstractClass() is a compile-time error, reported as "X is abstract; cannot be instantiated". The check happens before the program runs, because the type is deliberately incomplete. To obtain an object, create a concrete subclass and instantiate that, or return one from a factory method.

What is partial implementation in Java?


It is when a class inherits behaviour from a parent and leaves the rest for its children to finish. An abstract class is the tool for it: concrete methods hold the shared part, abstract methods mark what each child must write. In the example above, Notification shares recipient and log() while leaving send() and channel() open.

What is the difference between an abstract class and an incomplete class?


Java has no separate "incomplete class" construct. "Incomplete type" is a descriptive phrase for an abstract class, or for a concrete class whose parent left a method unimplemented. The compiler's job is to guarantee the incompleteness is always resolved somewhere before any object exists.

Can an abstract class have a constructor?


Yes. The constructor initialises state shared by every subclass and is invoked from them through super(...). It is never called on the abstract class itself, because that class cannot be instantiated. It is exactly the feature an interface cannot offer.

Can an abstract class have zero abstract methods?


Yes, and it compiles. The abstract keyword on its own blocks direct construction, which is useful when a class should only ever be obtained through a factory or a static builder.

What happens if a subclass does not implement all abstract methods?


The compiler reports that the class is not abstract and does not override the named method, unless the subclass is itself declared abstract. The obligation then passes to its own subclasses until some concrete class closes every gap.

Why can an abstract method not be private or static?


Because a private method is not inherited, so no child could ever see or replace it, and a static method belongs to the class rather than to instances, so there is no per-object behaviour to supply. javac rejects both as an illegal combination of modifiers.

Is abstract slower than a concrete class at run time?


No. There is no runtime check involved. The completeness requirement is enforced entirely at compile time, and the resulting object behaves exactly like any other. The only cost is a slightly deeper hierarchy, which is irrelevant next to the duplication it removes.

Should I use an empty method body instead of an abstract one?


No. An empty body compiles without complaint and produces a silent no-op at run time, which is far more expensive to find than a compile error. Declare the method abstract so a forgetful subclass fails the build immediately.

Can an abstract class implement an interface?


Yes, and it is common. The abstract class can implement the interface without providing every method, since it is itself abstract and cannot be instantiated anyway. Each concrete subclass then inherits the remaining obligation and must complete it.

Runnable Code Example: Abstract Classes in Java

Java 17+
Main.java
Run Online
// Shows what an abstract class can and cannot do: shared state and a
// shared algorithm stay in the parent, while the parts that genuinely
// vary are left abstract for each subclass to finish.
import java.util.ArrayList;
import java.util.List;
abstract class Notification {
    protected final String recipient;
    protected final String body;
    // A constructor on an abstract class is legal and necessary.
    // Subclasses reach it through super(...), so shared setup happens once.
    protected Notification(String recipient, String body) {
        this.recipient = recipient;
        this.body = body;
    } // CONCRETE: identical for every channel, so it is written once here.
    public void log() {
        System.out.println("[" + channel() + "] to " + recipient + ": " + body);
    } // ABSTRACT: a signature with no body. Every concrete subclass must
    // supply one, and the compiler refuses to build a class that does not.
    abstract void send();
    abstract String channel();
}final class EmailNotification extends Notification {
    private final String smtpServer;
    EmailNotification(String to, String body, String smtpServer) {
        super(to, body);
        this.smtpServer = smtpServer;
    } @Override void send() {
        System.out.println("  connecting to " + smtpServer);
    } @Override String channel() {
        return "EMAIL";
    }
}final class SmsNotification extends Notification {
    private final int segments;
    SmsNotification(String phone, String body) {
        super(phone, body);
        this.segments =(body.length() + 159) / 160;
    } @Override void send() {
        System.out.println("  sending " + segments + " SMS segment(s)");
    } @Override String channel() {
        return "SMS";
    }
}public class AbstractClassDemo {
    public static void main(String[] args) {
        List<Notification> queue = new ArrayList<>();
        queue.add(new EmailNotification("ada@example.com", "Build finished", "smtp.local"));
        queue.add(new SmsNotification("+8801700000000", "Your OTP is 4821"));
        // One loop over the abstract type, two different implementations.
        for (Notification n : queue) {
            n.log();
            n.send();
        } // A concrete subclass may be sealed against further extension.
        System.out.println("total queued: " + queue.size());
    }
}// Actual output:
// [EMAIL] to ada@example.com: Build finished
//   connecting to smtp.local
// [SMS] to +8801700000000: Your OTP is 4821
//   sending 1 SMS segment(s)
// total queued: 2

Common Java Technical Interview Questions & Answers

Interview Prep
Q1

Can an abstract class have a constructor if it cannot be instantiated?

Yes, and it is essential rather than contradictory. The constructor is invoked by subclasses through super(...) so that shared fields are initialised once, in one place, for the whole family. It is never called on the abstract class itself, because that class cannot be instantiated. An interface cannot offer this, since interfaces declare no constructors and hold no per-object state.
Q2

Can an abstract class have zero abstract methods?

Yes, and it compiles. The abstract keyword on its own blocks direct instantiation while every member stays usable through subclasses. This is useful when a class should only ever be obtained from a factory, and it costs nothing at run time because the restriction is enforced entirely at compile time.
Q3

Why does javac reject an abstract static method?

Because the two modifiers contradict each other. A static method belongs to the class rather than to instances, so there is no object whose behaviour it could describe, and therefore nothing for a subclass to supply. javac reports it as an illegal combination of modifiers. The same reasoning rules out private and final, since a private method is not inherited and a final one is locked.
Q4

What happens when a subclass skips one inherited abstract method?

The compiler rejects the class, naming the method it failed to implement, unless the subclass declares itself abstract. The duty then moves down to its own children. This is transitive, so no concrete object can ever exist with a hole in its behaviour.
Q5

Why is an empty concrete body weaker than an abstract method for a hook?

Because the empty version compiles silently and becomes a no-op at run time, where the failure surfaces far from its cause. Declaring the method abstract converts that omission into a compile error at the point of the mistake. For a template method hook, the abstract declaration is the cheaper failure by a wide margin.
Q6

A final method and an abstract method both restrict subclassing. What is the difference?

They restrict in opposite directions. A final concrete method is fully callable and stays exactly as written, it simply cannot be replaced, which is how you lock the skeleton of a template method. An abstract method is not callable at the base at all and must be supplied below, which is how you leave a deliberate gap. Final closes a decision, abstract opens one.
Q7

When does an abstract class beat an interface?

When the family shares state or constructor logic. An abstract class can hold private and protected fields and run code in its constructor, so every subclass funnels through the same initialisation. An interface can demand that a method exists but cannot supply shared data or a shared algorithm, so if those are needed, an abstract class is the only option.
Q8

Can a concrete subclass of an abstract class be declared final?

Yes, and it often should be. A final leaf closes the hierarchy so nobody can extend it further, while the abstract ancestor remains open to other direct subclasses. Marking leaves final is a cheap way to keep a design stable once the concrete types are settled.
Q9

Do I need a cast to reach an abstract method through a parent reference?

No. Calling through a supertype reference dispatches to the implementation on the actual object, exactly as with any override. A cast is only needed for a method that exists on the child but not on the parent, which is a separate concern from how the call is resolved.
Q10

Is there any run-time cost to the abstract mechanism?

None. There is no runtime check that a class is abstract and no dispatch penalty for the hierarchy. The completeness rule is a compile-time constraint, and the resulting object behaves identically to any other instance. The only cost is structural, a slightly deeper hierarchy, which is trivial next to the duplication it eliminates.

Key Takeaways, Best Practices & Clean Code Rules

Java Best Practice & Production Rules

Use an abstract class only when subclasses genuinely share state or code.
Keep shared fields private or protected and expose them through accessors.
Validate shared state in the base constructor so every subclass is checked.
Put the stable algorithm in a final method and leave only the varying steps abstract.
Declare hook methods abstract rather than empty, so a missing implementation fails the build.
Add @Override to every method that fills in an abstract declaration.
Keep the abstract surface minimal, since every open method is a permanent obligation.
Call super as the first statement in every constructor, before any field assignment.
Remember an abstract class may hold zero abstract methods, and that is not a mistake.
Do not mark an abstract method static, private, or final, since all three are rejected.
Mark concrete leaf classes final once the hierarchy is settled and nobody needs to extend them.
Store the loop variable at the abstract type so no cast or instanceof test is needed.
Prefer an interface when only a capability is being declared and no state is shared.
Do not build an abstract class with a single subclass and no plan for a second one.
Test through a parent reference to confirm the inherited concrete methods dispatch correctly.
Document which methods are deliberately left open, so subclasses know their obligations.
We value your privacy & transparency

OperateTools uses cookies and local storage to ensure basic tool functionality, preserve preferences, and analyze aggregated traffic. We never sell your personal data or index your document files. Learn more in our Privacy Policy.