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.
![]()
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.

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.

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:
| Combination | Why it cannot work |
|---|---|
abstract private | A private method is not visible to a subclass, so no subclass could ever supply the body the declaration demands. |
abstract static | A 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 final | final 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.

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.
| Choose | When |
|---|---|
abstract class | There is no sensible default for at least one operation, and an instance of the base alone would be meaningless. |
| Concrete base class | The base is a complete, usable object in its own right, and overriding is an optional refinement. |
abstract with no abstract methods | Every operation has a real implementation, but instantiating the base still means nothing. |
final class | The 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
| Mistake | What happens | Message |
|---|---|---|
new on an abstract class | compile error | Shape is abstract; cannot be instantiated |
| Concrete subclass misses one abstract method | compile error | Square is not abstract and does not override abstract method perimeter() in Shape |
| An abstract method in a class that is not abstract | compile error | NotAbstractClass is not abstract and does not override abstract method area() in NotAbstractClass |
| An abstract method with a body | compile error | abstract methods cannot have a body |
private abstract method | compile error | illegal combination of modifiers: abstract and private |
static abstract method | compile error | illegal combination of modifiers: abstract and static |
final abstract method | compile error | illegal combination of modifiers: abstract and final |
abstract final class | compile error | illegal combination of modifiers: abstract and final |
super.area() where area() is abstract | compile error | abstract method area() in Shape cannot be accessed directly |
Implementing a public hook as protected | compile error | attempting to assign weaker access privileges; was public |
Overriding a final template method | compile error | run() in Sneaky cannot override run() in Pipeline |
| Base constructor calls an abstract method | compiles, reads uninitialised state | none |
Concrete base with a do-nothing default instead of abstract | compiles, silently wrong answers | none |
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 classes — implements, 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.