Command Palette

Search for a command to run...

[Java Basics] Abstraction in Java: Abstract Classes and Abstract Methods

Every shape has an area. No shape is just a shape. That sentence is the whole idea behind the abstract keyword: Shape is real enough to declare variables of, pass to methods and store in a list, but too general to ever build one of.

Java gives you two tools for that. abstract on a class says "this type exists, but new on it is a compile error". abstract on a method says "every concrete subclass must supply this body, and the compiler will check". This article covers both, the traps around them, and the pattern abstract classes actually exist for. Every output line and every error message below was produced by compiling and running the code on OpenJDK 21.0.6.

A blocked new on a dashed base class, with two concrete subclasses below it

Two keywords, one rule each: abstract on a class blocks new, and abstract on a method stops a subclass from staying silent.

What abstraction means in Java

Abstraction here is not a vague design word. It is a concrete, checkable claim you make to the compiler: this concept has these operations, and I am not going to say how they work.

Take a drawing program. Circles, rectangles and triangles all have an area and a perimeter, and code that sums areas does not care which is which. So Shape is worth naming as a type. But the formula for "the area of a shape" does not exist — only the formula for the area of a particular shape does. Shape therefore has an area() in its API and no area() in its implementation.

That is exactly the shape of an abstract class:

  • it names the concept and its operations, so other code can be written against it;
  • it holds whatever the concept genuinely has in common — state, a constructor, real methods;
  • it leaves the operations that differ per subclass as declarations with no body.

The value is that the compiler now enforces the contract. A subclass that forgets area() does not compile, and nothing in the program can accidentally create a bare Shape and get a meaningless zero back.

The abstract keyword on a class

Put abstract in front of class and one thing changes: the class can no longer be instantiated.

An abstract class holding fields, a constructor and a real method plus two bodiless declarations, with two subclasses supplying them

abstract class Shape {
    private final String name;
    private final String color;

    protected Shape(String name, String color) {
        this.name = name;
        this.color = color;
    }

    public String getName() { return name; }
    public String getColor() { return color; }

    public abstract double area();
    public abstract double perimeter();

    public String describe() {
        return String.format("%s (%s): area=%.2f perimeter=%.2f",
                             name, color, area(), perimeter());
    }
}

class Circle extends Shape {
    private final double r;

    Circle(String color, double r) {
        super("Circle", color);
        this.r = r;
    }

    @Override public double area() { return Math.PI * r * r; }
    @Override public double perimeter() { return 2 * Math.PI * r; }
}

class Rectangle extends Shape {
    private final double w, h;

    Rectangle(String color, double w, double h) {
        super("Rectangle", color);
        this.w = w;
        this.h = h;
    }

    @Override public double area() { return w * h; }
    @Override public double perimeter() { return 2 * (w + h); }
}

public class ShapeDemo {
    public static void main(String[] args) {
        Shape[] shapes = { new Circle("red", 3), new Rectangle("blue", 3, 4) };
        for (Shape s : shapes) {
            System.out.println(s.describe());
        }
    }
}
Circle (red): area=28.27 perimeter=18.85
Rectangle (blue): area=12.00 perimeter=14.00

Notice how much of that class is ordinary Java. Two private final fields, a constructor that assigns them, three methods with real bodies — one of which, describe(), calls area() without knowing what area() does. Only two lines are unusual, and both end in a semicolon instead of a body.

Try to create the base type itself and javac refuses:

public class NewAbstract {
    public static void main(String[] args) {
        Shape s = new Shape();
        System.out.println(s.area());
    }
}
NewAbstract.java:7: error: Shape is abstract; cannot be instantiated
        Shape s = new Shape();
                  ^
1 error

This is a compile error, not a runtime one. The program never starts. Reflection does not get around it either — Shape.class.getDeclaredConstructor().newInstance() throws java.lang.InstantiationException at runtime, which is the same rule enforced by the JVM rather than by javac.

Why an abstract class still has a constructor

A constructor on a class you can never instantiate looks pointless, and it is the single most common misreading of abstract. The constructor is not there for new Shape(...). It is there for super("Circle", color).

Constructors are not inherited. When Circle needs to initialise the name and color fields that live in Shape, it cannot assign them directly — they are private to Shape — so it calls the constructor that can. protected is the conventional access level for exactly this reason: it is reachable from subclasses and from nowhere else useful. public would suggest a caller could invoke it directly, which no caller ever can.

The same reasoning explains the rest of the class. An abstract class can hold instance fields, static fields, static methods, initialiser blocks, nested classes, final methods, toString() overrides and even a main method that runs with java YourAbstractClass. abstract removes exactly one capability — being instantiated — and adds exactly one — being allowed to declare methods without bodies.

The abstract keyword on a method

An abstract method is a declaration with no body. It ends at the semicolon:

public abstract double area();

There is no { }, and adding one is an error in itself:

BodyAbstract.java:2: error: abstract methods cannot have a body
    public abstract double area() { return 0; }
                           ^
1 error

Declaring one has a consequence that reaches into every subclass: any class that extends Shape and is not itself abstract must provide a body for every abstract method it inherits. Forget one and the subclass is the thing that fails to compile, not the base class:

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

class Square extends Shape {
    private final double side;
    Square(double side) { this.side = side; }

    @Override public double area() { return side * side; }
}
Missing.java:6: error: Square is not abstract and does not override abstract method perimeter() in Shape
class Square extends Shape {
^
1 error

Read that message literally, because it names both ways out of it: Square is not abstract and does not override perimeter(). Fix either half and the error disappears.

The same message shows up in its strangest form when you put an abstract method in a class you forgot to mark abstract:

NotAbstractClass.java:1: error: NotAbstractClass is not abstract and does not override abstract method area() in NotAbstractClass
public class NotAbstractClass {
       ^
1 error

The class does not override its own method. Odd wording, but the rule behind it is uniform: a concrete class may not have unimplemented methods, wherever they came from.

The two ways out

Implement it. The subclass supplies the body, becomes concrete, and can be instantiated.

Or declare the subclass abstract too. A subclass is free to implement some of the abstract methods and leave the rest for its own subclasses. This is how you factor a hierarchy that has an intermediate level with real shared work:

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

// Way 1: implement every abstract method — the class becomes concrete.
class Square extends Shape {
    private final double side;
    Square(double side) { this.side = side; }

    @Override public double area() { return side * side; }
    @Override public double perimeter() { return 4 * side; }
}

// Way 2: implement some, stay abstract — the rest is the next subclass's job.
abstract class Ellipse extends Shape {
    protected final double a, b;
    protected Ellipse(double a, double b) { this.a = a; this.b = b; }

    @Override public double area() { return Math.PI * a * b; }
    // perimeter() is still abstract here, and that is legal
}

class Oval extends Ellipse {
    Oval(double a, double b) { super(a, b); }

    // Ramanujan's approximation
    @Override public double perimeter() {
        double h = Math.pow(a - b, 2) / Math.pow(a + b, 2);
        return Math.PI * (a + b) * (1 + 3 * h / (10 + Math.sqrt(4 - 3 * h)));
    }
}

public class WaysOut {
    public static void main(String[] args) {
        Shape sq = new Square(3);
        Shape ov = new Oval(4, 2);
        System.out.printf("Square area=%.2f perimeter=%.2f%n", sq.area(), sq.perimeter());
        System.out.printf("Oval   area=%.2f perimeter=%.2f%n", ov.area(), ov.perimeter());
    }
}
Square area=9.00 perimeter=12.00
Oval   area=25.13 perimeter=19.38

Ellipse implements area() and leaves perimeter() open, so it stays abstract and new Ellipse(4, 2) would fail with the same cannot be instantiated error. Oval closes the last hole and becomes usable.

Three ways to get a real object

new Shape() never compiles, so every object that exists at runtime is a subclass. There are exactly three shapes that can take.

new Shape() failing, against a concrete subclass, a still-abstract subclass and an anonymous subclass

The first two are the ones above: a concrete subclass such as Square, and a partially-implemented subclass such as Ellipse finished off by Oval. The third is written inline at the point of use.

Anonymous subclasses

new Shape() { ... } is not "instantiating an abstract class". It is a class declaration that happens to be written where an expression goes: javac compiles the braces into a real, nameless subclass, then instantiates that. You can watch it happen by printing the class name:

abstract class Shape {
    public abstract double area();
    public String tag() { return getClass().getName() + " -> " + area(); }
}

public class AnonDemo {
    public static void main(String[] args) {
        Shape unit = new Shape() {
            @Override public double area() { return 1.0; }
        };
        Shape half = new Shape() {
            @Override public double area() { return 0.5; }
        };
        System.out.println(unit.tag());
        System.out.println(half.tag());
        System.out.println("superclass of unit: " + unit.getClass().getSuperclass().getName());
        System.out.println("is anonymous: " + unit.getClass().isAnonymousClass());
    }
}
AnonDemo$1 -> 1.0
AnonDemo$2 -> 0.5
superclass of unit: Shape
is anonymous: true

The compiler really did emit two extra class files next to the others:

AnonDemo$1.class
AnonDemo$2.class
AnonDemo.class
Shape.class

The name is the enclosing class, a dollar sign, and a counter — first anonymous class in the file is $1, second is $2. That is where those puzzling Outer$1 entries in stack traces come from.

Two properties are worth knowing. An anonymous subclass can pass arguments to the abstract class's constructor, because it is a subclass like any other. And it can read effectively-final local variables from the enclosing method:

abstract class Task {
    protected final String name;
    protected Task(String name) { this.name = name; }
    public abstract int run(int input);
    public String toString() { return name + " (" + getClass().getName() + ")"; }
}

public class AnonState {
    public static void main(String[] args) {
        int factor = 3;                       // effectively final, captured
        Task triple = new Task("triple") {    // anonymous subclass CAN call super(...)
            @Override public int run(int input) { return input * factor; }
        };
        Task negate = new Task("negate") {
            @Override public int run(int input) { return -input; }
        };
        for (Task t : new Task[] { triple, negate }) {
            System.out.println(t + " -> " + t.run(7));
        }
    }
}
triple (AnonState$1) -> 21
negate (AnonState$2) -> -7

Anonymous subclasses are the right tool for a one-off implementation used in one place. Give it a name as soon as it is used twice, or as soon as the body outgrows a few lines.

Modifiers an abstract method cannot have

Three modifiers are illegal on an abstract method, and javac reports all three in one pass:

public abstract class AllThree {
    private abstract double a();
    static abstract double b();
    final abstract double c();
}
AllThree.java:2: error: illegal combination of modifiers: abstract and private
    private abstract double a();
                            ^
AllThree.java:3: error: illegal combination of modifiers: abstract and static
    static abstract double b();
                           ^
AllThree.java:4: error: illegal combination of modifiers: abstract and final
    final abstract double c();
                          ^
3 errors

Each one has a reason worth a sentence:

CombinationWhy it cannot work
abstract privateA private method is not visible to a subclass, so no subclass could ever supply the body the declaration demands.
abstract staticA static method is resolved against the class it is called on, not overridden by an instance's runtime type, so there is no dispatch that could reach a subclass body.
abstract finalfinal means "may not be overridden" and abstract means "must be overridden". The two are exact opposites.

The same collision applies to whole classes. abstract final class is rejected for identical reasons — nothing could ever subclass it, so nothing could ever implement it:

AbstractFinalClass.java:1: error: illegal combination of modifiers: abstract and final
public abstract final class AbstractFinalClass {
                      ^
1 error

Two related rules are easy to trip over. You cannot reach an abstract method through super — there is nothing there to call:

SuperAbstract.java:7: error: abstract method area() in Shape cannot be accessed directly
        return super.area() * 2;
                    ^
1 error

And you cannot narrow the access level when you implement one. A public abstract method must be implemented as public:

WeakerAccess.java:6: error: area() in Square cannot override area() in Shape
    @Override protected double area() { return 1; }
                               ^
  attempting to assign weaker access privileges; was public
1 error

protected abstract is the useful middle ground when the hook is an internal extension point rather than part of the public API — subclasses can implement it, callers cannot invoke it.

Can an abstract class have no abstract methods?

Yes, and it compiles without so much as a warning. An abstract class with a full set of bodies is a legitimate way to say do not instantiate this directly:

abstract class HttpStatus {
    public static final int OK = 200;
    public static final int NOT_FOUND = 404;

    public static boolean isSuccess(int code) {
        return code >= 200 && code < 300;
    }
}

public class NoAbstractMethods {
    public static void main(String[] args) {
        System.out.println(HttpStatus.isSuccess(HttpStatus.OK));
        System.out.println(HttpStatus.isSuccess(HttpStatus.NOT_FOUND));
    }
}
true
false

HttpStatus is a bag of constants and static helpers. An instance of it would mean nothing, and abstract makes that fact enforceable instead of a comment. The alternative — a single private constructor — blocks instantiation too and additionally blocks subclassing; pick that one when you also want the class closed. Neither is a substitute for abstract when the class is genuinely a base for others.

The reverse is also true: an abstract class may extend a concrete class. abstract class ValidatedWidget extends Widget is legal, and it adds an unimplemented method to a type that previously had none.

Mixing abstract and concrete methods

Shared implementation plus required extension points is the real reason to reach for an abstract class rather than something simpler. The base class carries everything the subclasses genuinely have in common, and names the few things they cannot share:

abstract class Account {
    private static int opened = 0;          // static state
    protected final String id;              // instance field
    private double balance;                 // mutable instance state

    protected Account(String id, double opening) {   // constructor, for super(...)
        this.id = id;
        this.balance = opening;
        opened++;
    }

    public static int openedCount() { return opened; }   // static method

    public double balance() { return balance; }          // concrete method

    public final void applyMonthEnd() {                  // concrete, uses the hook
        balance += balance * monthlyRate();
        balance -= fee();
    }

    protected abstract double monthlyRate();             // abstract hook
    protected abstract double fee();                     // abstract hook

    @Override public String toString() {
        return String.format("%s[%s] %.2f", getClass().getSimpleName(), id, balance);
    }
}

class Savings extends Account {
    Savings(String id, double opening) { super(id, opening); }
    @Override protected double monthlyRate() { return 0.004; }
    @Override protected double fee() { return 0; }
}

class Checking extends Account {
    Checking(String id, double opening) { super(id, opening); }
    @Override protected double monthlyRate() { return 0; }
    @Override protected double fee() { return 2.5; }
}

public class AccountDemo {
    public static void main(String[] args) {
        Account[] accounts = { new Savings("S-1", 1000), new Checking("C-1", 1000) };
        System.out.println("opened = " + Account.openedCount());
        for (Account a : accounts) {
            a.applyMonthEnd();
            System.out.println(a);
        }
    }
}
opened = 2
Savings[S-1] 1004.00
Checking[C-1] 997.50

balance is private mutable state on an abstract class, opened is a static counter it maintains, and applyMonthEnd() is arithmetic written once. Neither subclass repeats any of it. Each supplies two numbers — a rate and a fee — and nothing else. Every Savings and Checking shares the same money-handling code, so a bug fixed in applyMonthEnd() is fixed everywhere.

The template method pattern

applyMonthEnd() above is already an instance of the pattern that abstract classes exist for. The base class defines a final method that fixes the algorithm — the steps and their order — and calls abstract methods for the parts that vary. Subclasses fill in the steps and cannot touch the sequence.

A final export() running four steps, three of them abstract hooks filled differently by two subclasses

Here it is at full size: a report exporter where the base class owns the loop and the subclasses own the formatting.

import java.util.List;

abstract class ReportExporter {
    private final String title;
    private final String[] columns;
    private int rowsWritten = 0;

    protected ReportExporter(String title, String[] columns) {
        this.title = title;
        this.columns = columns;
    }

    protected String title() { return title; }
    protected String[] columns() { return columns; }
    public int rowsWritten() { return rowsWritten; }

    /** The algorithm. Fixed here, once, for every subclass. */
    public final String export(List<String[]> rows) {
        StringBuilder out = new StringBuilder();
        out.append(header());
        for (String[] row : rows) {
            String[] clean = new String[row.length];
            for (int i = 0; i < row.length; i++) clean[i] = escape(row[i]);
            out.append(formatRow(clean));
            rowsWritten++;
        }
        out.append(footer());
        return out.toString();
    }

    /** Shared implementation: every exporter needs it, none of them differ. */
    protected String escape(String cell) {
        return cell == null ? "" : cell.trim();
    }

    /* The extension points. */
    protected abstract String header();
    protected abstract String formatRow(String[] cells);
    protected abstract String footer();
}

Two subclasses, each supplying only the three hooks:

class CsvExporter extends ReportExporter {
    CsvExporter(String title, String[] columns) { super(title, columns); }

    @Override protected String header() {
        return String.join(",", columns()) + "\n";
    }

    @Override protected String formatRow(String[] cells) {
        return String.join(",", cells) + "\n";
    }

    @Override protected String footer() { return ""; }
}

class MarkdownExporter extends ReportExporter {
    MarkdownExporter(String title, String[] columns) { super(title, columns); }

    @Override protected String header() {
        return "## " + title() + "\n\n"
             + "| " + String.join(" | ", columns()) + " |\n"
             + "|" + "---|".repeat(columns().length) + "\n";
    }

    @Override protected String formatRow(String[] cells) {
        return "| " + String.join(" | ", cells) + " |\n";
    }

    @Override protected String footer() {
        return "\n" + rowsWritten() + " rows\n";
    }
}

public class ExportDemo {
    public static void main(String[] args) {
        List<String[]> rows = List.of(
            new String[] {"  Ada ", "Engineer"},
            new String[] {"Linus", "  Maintainer"});
        String[] cols = {"Name", "Role"};

        for (ReportExporter e : new ReportExporter[] {
                 new CsvExporter("Team", cols),
                 new MarkdownExporter("Team", cols) }) {
            System.out.println("--- " + e.getClass().getSimpleName() + " ---");
            System.out.print(e.export(rows));
            System.out.println("rowsWritten = " + e.rowsWritten());
            System.out.println();
        }
    }
}
--- CsvExporter ---
Name,Role
Ada,Engineer
Linus,Maintainer
rowsWritten = 2

--- MarkdownExporter ---
## Team

| Name | Role |
|---|---|
| Ada | Engineer |
| Linus | Maintainer |

2 rows
rowsWritten = 2

Two very different outputs from one algorithm. Note the untrimmed input: " Ada " and " Maintainer" both came out clean in both formats, because escape() runs in the base class and neither subclass had to remember it. That is the payoff — the steps every exporter must not get wrong are written once, in one place, and cannot be skipped.

Why the template method is final

export() is final on purpose. Without it, a subclass could override the whole method and quietly replace the algorithm — the exact failure the pattern exists to prevent. final turns that from a silent bug into a compile error:

abstract class Pipeline {
    public final String run() { return step(); }
    protected abstract String step();
}

class Sneaky extends Pipeline {
    @Override protected String step() { return "step"; }

    @Override public String run() { return "I skipped the algorithm"; }
}
OverrideFinal.java:9: error: run() in Sneaky cannot override run() in Pipeline
    @Override public String run() { return "I skipped the algorithm"; }
                            ^
  overridden method is final
1 error

The division of labour is worth stating explicitly, because it is what makes the pattern readable: final means this is mine, and it is settled; abstract means this is yours, and you must supply it; a plain method with a body means this is a default, override it if you have a reason to. The last category is the "hook" in the classic description of the pattern — an empty or trivially-implemented method the base class calls at a point where most subclasses do nothing.

Polymorphism through an abstract type

Because every abstract method is guaranteed to have an implementation in every concrete subclass, the abstract type is safe to use as the element type of an array or a List. One loop, one call, and each object runs its own body:

import java.util.List;

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

class Circle extends Shape {
    private final double r;
    Circle(double r) { this.r = r; }
    @Override public double area() { return Math.PI * r * r; }
    @Override public String name() { return "Circle"; }
}

class Rect extends Shape {
    private final double w, h;
    Rect(double w, double h) { this.w = w; this.h = h; }
    @Override public double area() { return w * h; }
    @Override public String name() { return "Rect"; }
}

class Triangle extends Shape {
    private final double b, h;
    Triangle(double b, double h) { this.b = b; this.h = h; }
    @Override public double area() { return b * h / 2; }
    @Override public String name() { return "Triangle"; }
}

public class PolyDemo {
    public static void main(String[] args) {
        List<Shape> shapes = List.of(new Circle(1), new Rect(3, 4), new Triangle(6, 5));

        double total = 0;
        for (Shape s : shapes) {
            System.out.printf("%-8s %8.3f%n", s.name(), s.area());
            total += s.area();
        }
        System.out.printf("%-8s %8.3f%n", "TOTAL", total);

        Shape biggest = shapes.stream().max((a, b) -> Double.compare(a.area(), b.area())).get();
        System.out.println("biggest = " + biggest.name());
    }
}
Circle      3.142
Rect       12.000
Triangle   15.000
TOTAL      30.142
biggest = Triangle

The mechanism that picks the right area() at each iteration is dynamic dispatch, covered in the previous article — nothing about it changes because the declared type is abstract. What changes is the guarantee. With a concrete base class you are hoping every subclass overrode area(); with an abstract one the compiler already checked. There is no instanceof here, no switch on a type tag, and adding a fourth shape requires no edit to this loop at all.

Constructors in an abstract hierarchy

An abstract base's constructor runs exactly like any other superclass constructor: first, before the subclass constructor body, as the first thing new does after allocating the object.

abstract class Shape {
    protected final String name;

    protected Shape(String name) {
        System.out.println("2. Shape(String) body, name = " + name);
        this.name = name;
    }
}

class Circle extends Shape {
    private final double r;

    Circle(double r) {
        super("Circle");
        System.out.println("3. Circle(double) body, r = " + r);
        this.r = r;
    }
}

public class CtorOrder {
    public static void main(String[] args) {
        System.out.println("1. before new Circle(3)");
        new Circle(3);
        System.out.println("4. after new Circle(3)");
    }
}
1. before new Circle(3)
2. Shape(String) body, name = Circle
3. Circle(double) body, r = 3.0
4. after new Circle(3)

The chain is the same one the article on inheritance described; being abstract changes nothing about it. What is new is the trap that ordering opens up.

The trap: calling an abstract method from a constructor

The rule from the previous article — never call an overridable method from a constructor — bites hardest here, because an abstract method gives you no way to opt out. There is no base implementation to fall back on, so the call always lands in a subclass override, and it lands there before the subclass constructor body has run.

abstract class Shape {
    protected Shape() {
        System.out.println("in Shape(): area() = " + area());
    }
    public abstract double area();
}

class Square extends Shape {
    private final double side;

    Square(double side) {
        this.side = side;
        System.out.println("in Square(): area() = " + area());
    }

    @Override public double area() { return side * side; }
}
in Shape(): area() = 0.0
in Square(): area() = 16.0

side is final and was assigned 4, and area() still returned 0.0 — because Square.area() ran before this.side = side did. Nothing warns you. A concrete base method could at least leave a usable default behind; an abstract one cannot, which is why the constructor of an abstract class is the last place to call one.

Abstract class or concrete base class?

The alternative to abstract is a normal class with overridable methods and a harmless-looking default. It compiles, and it fails silently:

class Shape {                       // concrete base, not abstract
    public double area() { return 0; }   // a "default" nobody meant to use
}

class Circle extends Shape {
    private final double r;
    Circle(double r) { this.r = r; }
    @Override public double area() { return Math.PI * r * r; }
}

class Hexagon extends Shape {       // author forgot area() entirely
    private final double side;
    Hexagon(double side) { this.side = side; }
}

public class ConcreteBase {
    public static void main(String[] args) {
        Shape[] shapes = { new Circle(1), new Hexagon(1), new Shape() };
        double total = 0;
        for (Shape s : shapes) {
            System.out.printf("%-8s %.4f%n", s.getClass().getSimpleName(), s.area());
            total += s.area();
        }
        System.out.printf("total    %.4f%n", total);
    }
}
Circle   3.1416
Hexagon  0.0000
Shape    0.0000
total    3.1416

No error, no warning, and a total that is wrong by however much the hexagon was worth. Making Shape abstract turns both defects into compile errors: Hexagon fails to build, and new Shape() is rejected outright.

ChooseWhen
abstract classThere is no sensible default for at least one operation, and an instance of the base alone would be meaningless.
Concrete base classThe base is a complete, usable object in its own right, and overriding is an optional refinement.
abstract with no abstract methodsEvery operation has a real implementation, but instantiating the base still means nothing.
final classThe class is complete and inheritance would only create ways to break it.

The test that decides it: write down what new Base() would mean. If you cannot finish the sentence, the class is abstract.

How an abstract class differs from an interface

The short version, since the next article owns the full comparison. Three differences do most of the work. A class can extend exactly one class but implement any number of interfaces — class Square extends Shape, Printable does not even parse, javac reports error: '{' expected at the comma. An abstract class can hold mutable instance state and a constructor, which is what let Account keep a balance and ReportExporter keep a rowsWritten counter; an interface has neither, and its fields are implicitly public static final constants. And an abstract class can keep members protected or package-private, while everything an interface declares is implicitly public. As a first approximation: an abstract class shares implementation down one line of descent, an interface declares a capability that unrelated types can each claim.

Common mistakes and the errors they produce

MistakeWhat happensMessage
new on an abstract classcompile errorShape is abstract; cannot be instantiated
Concrete subclass misses one abstract methodcompile errorSquare is not abstract and does not override abstract method perimeter() in Shape
An abstract method in a class that is not abstractcompile errorNotAbstractClass is not abstract and does not override abstract method area() in NotAbstractClass
An abstract method with a bodycompile errorabstract methods cannot have a body
private abstract methodcompile errorillegal combination of modifiers: abstract and private
static abstract methodcompile errorillegal combination of modifiers: abstract and static
final abstract methodcompile errorillegal combination of modifiers: abstract and final
abstract final classcompile errorillegal combination of modifiers: abstract and final
super.area() where area() is abstractcompile errorabstract method area() in Shape cannot be accessed directly
Implementing a public hook as protectedcompile errorattempting to assign weaker access privileges; was public
Overriding a final template methodcompile errorrun() in Sneaky cannot override run() in Pipeline
Base constructor calls an abstract methodcompiles, reads uninitialised statenone
Concrete base with a do-nothing default instead of abstractcompiles, silently wrong answersnone

The last two produce no diagnostic at all, and they are the two this article spends the most time on for that reason.

FAQ

Can you instantiate an abstract class in Java?

No. new Shape() is rejected at compile time with Shape is abstract; cannot be instantiated, and the JVM enforces the same rule for anything that gets past the compiler — Shape.class.getDeclaredConstructor().newInstance() throws java.lang.InstantiationException. What looks like instantiating one, new Shape() { ... }, is really a nameless subclass being declared and then instantiated; getClass().getName() on the result prints something like AnonDemo$1.

Can an abstract class have a constructor in Java?

Yes, and most useful ones do. It is never called by new on the abstract class itself; it is called by every subclass constructor through super(...), explicitly or implicitly, and it runs before the subclass constructor body. protected is the conventional access level, since subclasses are the only legitimate callers. The one thing not to do inside it is call an abstract method — the subclass override will run against fields that are still at their default values.

Can an abstract class have no abstract methods?

Yes, and it compiles cleanly. It is a way of saying "this class exists to be extended or used statically, never instantiated", which is enforceable in a way a comment is not. A private constructor achieves the same block on instantiation while also preventing subclassing, so choose that when the class should be closed as well.

Why can an abstract method not be private, static or final?

All three contradict what abstract means. private hides the method from subclasses, so nothing could implement it. static methods are not dispatched on an instance's runtime type, so there is no mechanism by which a subclass body could be reached. final forbids overriding, which is exactly what abstract requires. javac reports each as illegal combination of modifiers.

When should I use an abstract class instead of a normal base class?

When at least one operation has no sensible default and an instance of the base alone would be meaningless. A concrete base forces you to invent a default — usually a return 0 or an empty method — and a subclass that forgets to override it produces wrong answers with no error. Making the base abstract converts both mistakes into compile errors. If every operation genuinely has a correct default and the base object is useful on its own, a concrete class is the simpler choice.

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

An abstract class can hold constructors, mutable instance state and non-public members, and a class may extend only one of them. An interface declares a capability that any number of unrelated types can implement, but has no instance state and no constructor. Roughly: reach for an abstract class to share implementation down a single family, and for an interface to describe what a type can do. The next article works through the whole comparison in detail.

Conclusion

abstract on a class removes one capability and adds one. It removes new, so the base type can only ever be used to declare, pass and store; and it permits methods with no body, so the compiler can force every concrete subclass to supply the parts that genuinely differ. Everything else about the class is unchanged — fields, a constructor to be reached through super(...), static members and methods with real code all stay exactly as they were, which is why "abstract means empty" is the misreading worth unlearning first.

The pattern that justifies the keyword is the template method: a final method in the base that fixes the order of the steps, plus abstract hooks the subclasses fill. CsvExporter and MarkdownExporter produced completely different output without either of them touching the loop, the escaping or the row counter. Around that sit the rules worth memorising — an abstract method cannot be private, static or final, a subclass either implements everything or is declared abstract itself, and a base constructor must never call an abstract method.

Next in this series: interfaces, and how they compare with abstract classesimplements, why a class can have many interfaces but only one superclass, default and static methods on an interface, and the full side-by-side comparison with everything above.

Related Posts

[Java Basics] Interfaces in Java, and How They Differ from Abstract Classes

Interfaces in Java on OpenJDK 21: implements versus extends, implicit public static final members, one class with many interfaces, default, static and private methods, the diamond problem and Interface.super, the class-wins rule, functional interfaces, and a precise comparison against abstract classes.

[Java Basics] Recursion in Java: How It Works and When to Use It

How recursion works in Java: the base case and the recursive case, factorial traced frame by frame, a real StackOverflowError from a missing base case, recursion depth and -Xss, why naive Fibonacci needs 2692537 calls for fib(30) while memoisation needs 59, recursion versus iteration, and why the JVM does not optimise tail calls.

[Java Basics] Encapsulation in Java: Access Modifiers, Getters and Setters

Encapsulation in Java explained by running it: the four access modifiers demonstrated across two real packages, the protected rule most tutorials state wrongly, getters that leak a mutable field, defensive copies, immutable classes and records, with every javac error reproduced on JDK 21.

[Java Basics] Methods in Java: Declaring and Calling Them

How to declare and call a method in Java: the parts of a declaration, static versus instance methods and the non-static method cannot be referenced from a static context error, the return statement, the call stack and StackOverflowError, reading a stack trace, Javadoc, and the real compiler errors beginners hit.