Java Tutorial
🔍

Java Abstract Classes

Java Abstract Classes

An abstract class sits in the middle of the abstraction spectrum — more abstract than a regular class, less abstract than an interface. It cannot be instantiated directly, but it can contain both abstract methods (which subclasses must implement) and concrete methods (which subclasses inherit and can optionally override). It can hold fields, constructors, and any level of access modifier.

The abstract class's value is precisely this middle ground. When multiple classes share common logic — a fixed algorithm, shared fields, shared validation — but differ in specific steps, the abstract class holds the shared parts and declares the variable parts as abstract. Subclasses fill in the gaps.

What Is an Abstract Class in Java?

An abstract class is a class declared with the abstract keyword that cannot be instantiated using new. It serves as a base class that other classes extend. It can contain:

  • ›Abstract methods — declared without a body using the abstract keyword; every concrete subclass must implement them
  • ›Concrete methods — fully implemented; subclasses inherit and may override them
  • ›Instance fields — regular fields; subclasses inherit them and access them via getters or through super
  • ›Constructors — cannot be called directly, but called through super() in subclass constructors
  • ›Static methods and fields — accessible normally through the class name

Abstract Class vs Regular Class vs Interface

AspectRegular ClassAbstract ClassInterface
abstract keywordNoYesNot applicable
InstantiationYes — newNo — cannot instantiateNo — cannot instantiate
Abstract methodsNoYes — abstract keywordYes — by default
Concrete methodsYesYesdefault and static only (Java 8+)
Instance fieldsYesYesNo — only constants
ConstructorsYesYes — called via super()No
Multiple inheritanceNo — extends oneNo — extends oneYes — implements many
Access modifiersAnyAnypublic by default
When to useComplete implementationPartial implementation + shared logicPure contract + capability definition

Declaring an Abstract Class

// File: Shape.java public abstract class Shape { // Instance field — shared by all shapes private final String colour; // Constructor — called by subclass constructors via super() public Shape(String colour) { if (colour == null || colour.isBlank()) { throw new IllegalArgumentException("Colour is required."); } this.colour = colour; } public String getColour() { return colour; } // Abstract methods — every subclass MUST implement these public abstract double area(); public abstract double perimeter(); // Concrete method — shared across all shapes, no override needed public String describe() { return getClass().getSimpleName() + " | Colour: " + colour + " | Area: " + String.format("%.2f", area()) + " | Perimeter: " + String.format("%.2f", perimeter()); } }
// File: Circle.java public class Circle extends Shape { private final double radius; public Circle(double radius, String colour) { super(colour); // calls Shape constructor if (radius <= 0) throw new IllegalArgumentException("Radius must be positive."); this.radius = radius; } public double getRadius() { return radius; } @Override public double area() { return Math.PI * radius * radius; } @Override public double perimeter() { return 2 * Math.PI * radius; } }
// File: Rectangle.java public class Rectangle extends Shape { private final double width; private final double height; public Rectangle(double width, double height, String colour) { super(colour); if (width <= 0 || height <= 0) { throw new IllegalArgumentException("Width and height must be positive."); } this.width = width; this.height = height; } public double getWidth() { return width; } public double getHeight() { return height; } @Override public double area() { return width * height; } @Override public double perimeter() { return 2 * (width + height); } }
// File: ShapeDemo.java import java.util.List; public class ShapeDemo { public static void main(String[] args) { // Cannot do: new Shape("Red") — compile error List<Shape> shapes = List.of( new Circle(7.0, "Blue"), new Rectangle(5.0, 3.0, "Red"), new Circle(4.5, "Green") ); for (Shape shape : shapes) { System.out.println(shape.describe()); // concrete method — runtime dispatch } System.out.println("\nTotal area: " + String.format("%.2f", shapes.stream() .mapToDouble(Shape::area) .sum())); } }
Output:
Circle | Colour: Blue | Area: 153.94 | Perimeter: 43.98
Rectangle | Colour: Red | Area: 15.00 | Perimeter: 16.00
Circle | Colour: Green | Area: 63.62 | Perimeter: 28.27

Total area: 232.56

Shape cannot be instantiated — it is a concept, not a specific thing. Circle and Rectangle are concrete shapes. The abstract class holds what all shapes share — a colour field, a constructor that validates it, and a describe() method that formats any shape's output. The variable parts — how to calculate area and perimeter — are abstract. Each subclass fills them in.

The Template Method Pattern

The Template Method Pattern is the most important design pattern built on abstract classes. It defines the skeleton of an algorithm in a concrete method of the abstract class, deferring certain steps to abstract methods that subclasses implement. The flow is fixed; the steps are variable.

// File: DataProcessor.java public abstract class DataProcessor { // Template method — final so subclasses cannot change the flow public final ProcessingResult process(String rawData) { System.out.println("[" + getProcessorName() + "] Starting processing..."); // Step 1 — validate input (subclass decides validation rules) if (!validate(rawData)) { return new ProcessingResult(false, "Validation failed.", null); } // Step 2 — transform data (subclass decides how) String transformed = transform(rawData); // Step 3 — post-process (optional — default is identity) String finalData = postProcess(transformed); System.out.println("[" + getProcessorName() + "] Done. Output length: " + finalData.length() + " chars."); return new ProcessingResult(true, "Success", finalData); } // Abstract steps — subclasses define these protected abstract String getProcessorName(); protected abstract boolean validate(String data); protected abstract String transform(String data); // Hook method — default behaviour provided, subclass may override protected String postProcess(String data) { return data; // default: no post-processing } // Result holder public record ProcessingResult(boolean success, String message, String output) {} }
// File: JsonSanitiser.java public class JsonSanitiser extends DataProcessor { @Override protected String getProcessorName() { return "JSON-SANITISER"; } @Override protected boolean validate(String data) { if (data == null || data.isBlank()) { System.out.println(" Validation: Empty data rejected."); return false; } if (!data.trim().startsWith("{") && !data.trim().startsWith("[")) { System.out.println(" Validation: Not valid JSON structure."); return false; } System.out.println(" Validation: OK"); return true; } @Override protected String transform(String data) { // Strips newlines and extra spaces for compact JSON String compact = data.replaceAll("\\s+", " ").trim(); System.out.println(" Transform: Compacted JSON."); return compact; } @Override protected String postProcess(String data) { // Wraps in a standard envelope return "{\"data\":" + data + ",\"sanitised\":true}"; } }
// File: CsvNormaliser.java public class CsvNormaliser extends DataProcessor { private final char delimiter; public CsvNormaliser(char delimiter) { this.delimiter = delimiter; } @Override protected String getProcessorName() { return "CSV-NORMALISER"; } @Override protected boolean validate(String data) { if (data == null || data.isBlank()) { System.out.println(" Validation: Empty CSV rejected."); return false; } long lineCount = data.lines().count(); System.out.println(" Validation: " + lineCount + " lines found. OK"); return true; } @Override protected String transform(String data) { // Converts all lines to uppercase and trims each field String normalised = data.lines() .map(line -> java.util.Arrays.stream(line.split(String.valueOf(delimiter))) .map(String::trim) .map(String::toUpperCase) .reduce((a, b) -> a + delimiter + b) .orElse("")) .reduce((a, b) -> a + "\n" + b) .orElse(""); System.out.println(" Transform: Normalised to uppercase."); return normalised; } // postProcess not overridden — uses default (identity) }
// File: TemplateMethodDemo.java public class TemplateMethodDemo { public static void main(String[] args) { DataProcessor jsonProcessor = new JsonSanitiser(); DataProcessor csvProcessor = new CsvNormaliser(','); System.out.println("=== JSON Sanitiser ==="); DataProcessor.ProcessingResult jsonResult = jsonProcessor.process( "{ \"orderId\" : \"ORD-001\" , \"amount\" : 1499 }" ); System.out.println("Result: " + jsonResult.message()); System.out.println("Output: " + jsonResult.output()); System.out.println("\n=== CSV Normaliser ==="); DataProcessor.ProcessingResult csvResult = csvProcessor.process( " sku-001 , laptop , 85000\n sku-002 , mouse , 1299 " ); System.out.println("Result: " + csvResult.message()); System.out.println("Output:\n" + csvResult.output()); System.out.println("\n=== Invalid Input ==="); DataProcessor.ProcessingResult invalid = jsonProcessor.process(""); System.out.println("Result: " + invalid.message()); } }
Output:
=== JSON Sanitiser ===
[JSON-SANITISER] Starting processing...
  Validation: OK
  Transform: Compacted JSON.
Result: Success
Output: {"data":{ "orderId" : "ORD-001" , "amount" : 1499 },"sanitised":true}

=== CSV Normaliser ===
[CSV-NORMALISER] Starting processing...
  Validation: 2 lines found. OK
  Transform: Normalised to uppercase.
Result: Success
Output:
SKU-001,LAPTOP,85000
SKU-002,MOUSE,1299

=== Invalid Input ===
[JSON-SANITISER] Starting processing...
  Validation: Empty data rejected.
Result: Validation failed.

process() is final — it controls the algorithm flow and cannot be overridden. validate(), transform(), and postProcess() are abstract or hook methods — each subclass fills them in. The caller uses DataProcessor references without knowing whether it is working with JsonSanitiser or CsvNormaliser. This is the Template Method Pattern in full production form.

Abstract Classes and Constructors

Abstract classes can have constructors — and they must, when they hold fields that subclasses need to initialise.

// File: BaseReport.java public abstract class BaseReport { private final String reportId; private final String preparedBy; private final java.time.LocalDate reportDate; // Protected constructor — accessible to subclasses via super() protected BaseReport(String reportId, String preparedBy) { if (reportId == null || reportId.isBlank()) { throw new IllegalArgumentException("Report ID required."); } this.reportId = reportId; this.preparedBy = preparedBy; this.reportDate = java.time.LocalDate.now(); } public String getReportId() { return reportId; } public String getPreparedBy() { return preparedBy; } protected String getReportHeader() { return "Report ID : " + reportId + "\nPrepared by: " + preparedBy + "\nDate : " + reportDate; } // Abstract — each report type provides its own body public abstract String generateBody(); // Concrete template — assembles header + body public final String generate() { return "=".repeat(40) + "\n" + getReportHeader() + "\n" + "=".repeat(40) + "\n" + generateBody(); } }
// File: SalesReport.java public class SalesReport extends BaseReport { private final double totalRevenue; private final int totalOrders; private final String region; public SalesReport(String reportId, String preparedBy, double totalRevenue, int totalOrders, String region) { super(reportId, preparedBy); // initialises BaseReport fields this.totalRevenue = totalRevenue; this.totalOrders = totalOrders; this.region = region; } @Override public String generateBody() { double avgOrder = totalOrders > 0 ? totalRevenue / totalOrders : 0; return "Region : " + region + "\nTotal orders : " + totalOrders + "\nTotal revenue: Rs." + String.format("%.2f", totalRevenue) + "\nAvg order : Rs." + String.format("%.2f", avgOrder); } }
// File: ConstructorDemo.java public class ConstructorDemo { public static void main(String[] args) { SalesReport report = new SalesReport( "RPT-2024-001", "Ananya Krishnan", 485000.0, 320, "South India" ); System.out.println(report.generate()); } }
Output:
========================================
Report ID : RPT-2024-001
Prepared by: Ananya Krishnan
Date       : 2024-01-15
========================================
Region       : South India
Total orders : 320
Total revenue: Rs.485000.00
Avg order    : Rs.1515.63

The abstract class BaseReport holds the reportId, preparedBy, and reportDate fields — these are common to every report. Its constructor validates and initialises them. SalesReport calls super(reportId, preparedBy) to delegate this initialisation. The abstract class constructor cannot be called directly — it is always via super() from a subclass.

Abstract Method Rules

RuleDescription
abstract keywordRequired in the method declaration
No bodyCannot have {} — not even empty braces
Must be overriddenEvery concrete subclass must implement all abstract methods
Cannot be privatePrivate methods are not visible to subclasses — cannot be overridden
Cannot be staticStatic methods belong to the class — no override mechanism
Cannot be finalfinal prevents overriding — contradicts the purpose of abstract
Cannot be in regular classAbstract methods can only exist in abstract classes or interfaces
Subclass can be abstract tooA subclass that does not implement all abstract methods must itself be abstract

When Abstract Class — When Interface

ScenarioUse Abstract ClassUse Interface
Shared fields neededYes — instance fields are allowedNo — only constants
Shared constructor logicYesNo — no constructors
Shared concrete method logicYesOnly via default methods
Multiple inheritance neededNo — single extendsYes — multiple implements
Pure contract / capabilityNo — overhead of classYes — cleaner
IS-A type hierarchyYes — when hierarchy makes senseYes — when capability-based
State requiredYesNo
Evolving an existing APIYes — add methods freelyCarefully — breaks non-default implementations
Framework hooks (Template Method)Yes — natural fitAwkward
Lightweight role definitionNoYes

Abstract Class — What Can and Cannot Be Done

FeatureAllowedNotes
abstract methodsYesMust be implemented by concrete subclasses
Concrete methodsYesInherited by subclasses
Instance fieldsYesEncapsulated in the abstract class
Static fieldsYesShared class-level data
ConstructorsYesCalled via super() from subclass
static abstract methodNoContradictory — static has no dispatch
private abstract methodNoPrivate methods not visible to subclasses
final abstract methodNofinal prevents override; abstract requires it
Instantiation with newNoCompile-time error
Partial implementationYesCan implement some but not all abstract methods
Implement interfaceYesAbstract class can implement interface, leave methods abstract

Basic Example

A simple Animal abstract class holds a shared name field and a concrete describe() method, while leaving the actual sound each animal makes as an abstract method for subclasses to fill in.

public class Main { public static void main(String[] args) { Animal dog = new Dog("Rex"); Animal cat = new Cat("Whiskers"); dog.describe(); cat.describe(); } } abstract class Animal { private final String name; Animal(String name) { this.name = name; } abstract String makeSound(); void describe() { System.out.println(name + " says " + makeSound()); } } class Dog extends Animal { Dog(String name) { super(name); } @Override String makeSound() { return "Woof"; } } class Cat extends Animal { Cat(String name) { super(name); } @Override String makeSound() { return "Meow"; } }

Output

Rex says Woof
Whiskers says Meow

How it works:

  1. ›Animal cannot be instantiated directly — new Animal("X") would not compile — but it still holds the shared name field and constructor logic every subclass needs.
  2. ›makeSound() has no body in Animal; it is a promise that every concrete subclass must keep.
  3. ›Dog and Cat each provide their own makeSound(), and the shared describe() method calls it polymorphically — describe() never needs to know which subclass it is running on.

Best Practices

Mark the template method as final

The template method defines the algorithm flow — it should not be overridable by subclasses. Making it final enforces the invariant that the sequence of steps never changes, only the steps themselves. A subclass that changes the flow breaks every caller that depends on the guaranteed sequence.

Minimise the number of abstract methods

Every abstract method is a burden on every concrete subclass — including test doubles. Keep the abstract contract as small as possible. If a step has a reasonable default, make it a hook method with a default implementation rather than abstract. Subclasses override only when they need different behaviour.

Use protected for abstract and hook methods — not public

Abstract and hook methods are implementation details of the algorithm. They should be callable from within the abstract class and overridable by subclasses — but not directly invocable by external callers. Declare them protected. External callers should only use the public template method.

Declare the abstract class with a protected constructor when possible

An abstract class with a public constructor signals that external callers can call it — but they cannot (abstract classes cannot be instantiated). Using protected is more accurate: only subclass constructors call it through super(). This prevents the misleading implication that direct instantiation is possible.

Common Mistakes

Mistake 1 — Trying to Instantiate an Abstract Class

public abstract class Vehicle { public abstract String getType(); } public class Main { public static void main(String[] args) { Vehicle v = new Vehicle(); // compile error } }
Compile error: Vehicle is abstract; cannot be instantiated

Abstract classes exist to be extended, not instantiated. To get an object, create a concrete subclass. To declare a variable, use the abstract type — Vehicle v = new Car(...) is valid; new Vehicle() is not.

Mistake 2 — Making Abstract Methods private or static

public abstract class Processor { // Compile error — abstract and private are contradictory private abstract void process(); // private means not visible to subclass // Compile error — abstract and static are contradictory public static abstract void reset(); // static methods cannot be overridden }
Compile error: illegal combination of modifiers: abstract and private
Compile error: illegal combination of modifiers: abstract and static

Abstract methods require subclasses to override them — which requires the methods to be visible (private prevents this) and to participate in dynamic dispatch (static prevents this). Neither combination makes logical sense.

Mistake 3 — Not Implementing All Abstract Methods in a Concrete Subclass

public abstract class Animal { public abstract String sound(); public abstract String habitat(); } // Missing habitat() — compile error unless class is also abstract public class Dog extends Animal { @Override public String sound() { return "Woof"; } // habitat() not implemented — compiler error }
Compile error: Dog is not abstract and does not override abstract method habitat() in Animal

Every concrete subclass must implement every abstract method from the entire parent chain. If a subclass cannot implement all of them, declare it abstract too — a concrete grandchild will eventually complete the chain.

Mistake 4 — Choosing Abstract Class When Interface Is Correct

// Wrong — no shared state, no shared implementation // Should be an interface public abstract class Serialisable { public abstract byte[] serialise(); public abstract void deserialise(byte[] data); // No fields, no concrete methods — an interface is cleaner and more flexible } // Correct — interface allows multiple implementation and imposes no class hierarchy public interface Serialisable { byte[] serialise(); void deserialise(byte[] data); }

When an abstract class has only abstract methods and no fields or concrete logic, it is functionally identical to an interface — but more restrictive, because implementing classes lose their single extends slot. Choose an interface unless you genuinely need shared fields or concrete method implementations.

Interview Questions

Q1. What is an abstract class in Java and when should you use it?

An abstract class is a class declared with abstract that cannot be instantiated. It can contain abstract methods — which subclasses must implement — and concrete methods that subclasses inherit. Use an abstract class when multiple classes share common fields, common constructor logic, or shared concrete method implementations, but differ in certain specific behaviours. If no shared implementation or state is needed, an interface is the cleaner choice.

Q2. What is the difference between an abstract class and an interface in Java?

An abstract class can have instance fields, constructors, and both abstract and concrete methods — but a class can extend only one. An interface defines abstract method signatures, can have default and static methods since Java 8, has no instance fields or constructors, and a class can implement multiple interfaces simultaneously. Use an abstract class for partial shared implementation in a type hierarchy. Use an interface for pure contracts, capability definitions, or when multiple type inheritance is needed.

Q3. Can an abstract class have a constructor in Java?

Yes. Abstract classes can have constructors — and they are necessary when the abstract class holds fields that subclasses need to initialise. The constructor is never called directly (since the class cannot be instantiated) but is always called through super() in a subclass constructor. Making the constructor protected is a common practice to signal that it is meant only for subclass use.

Q4. What is the Template Method Pattern and how does it use abstract classes?

The Template Method Pattern defines the skeleton of an algorithm in a final concrete method of an abstract class, deferring variable steps to abstract methods that subclasses implement. The abstract class controls the fixed algorithm sequence; the subclasses fill in the specific behaviours. The template method is final so no subclass can change the flow. This pattern is the canonical use case for abstract classes — fixing the what while leaving the how open for specialisation.

Q5. What is the difference between an abstract method and a hook method?

An abstract method has no body and forces every concrete subclass to provide an implementation — if a subclass does not, it must itself be declared abstract. A hook method is a concrete method in the abstract class with a default implementation that subclasses may optionally override. Hook methods allow customisation without obligation — subclasses that need different behaviour override; others inherit the default. Hook methods reduce the burden on subclasses compared to abstract methods.

Q6. Can an abstract class implement an interface in Java?

Yes. An abstract class can implement an interface without implementing all of the interface's abstract methods — the unimplemented methods become abstract methods of the abstract class, to be fulfilled by its concrete subclasses. This is a common layered design: an interface defines the contract, an abstract class provides shared implementation for some methods, and concrete classes complete the remaining abstract ones.

FAQs

What is an abstract class in Java?

An abstract class is a class declared with the abstract keyword that cannot be instantiated with new. It can contain abstract methods that subclasses must implement, concrete methods that subclasses inherit, fields, and constructors. It is used as a base class when multiple subclasses share some common implementation but differ in specific behaviours.

What is the difference between abstract class and interface?

An abstract class can have instance fields, constructors, and both abstract and concrete methods — a class can extend only one. An interface has no instance fields or constructors, all methods are abstract by default (with default/static methods available since Java 8) — a class can implement many. Use abstract class for shared implementation; use interface for pure contracts or multiple type inheritance.

Can abstract class have concrete methods in Java?

Yes. Abstract classes can have fully implemented concrete methods. These are inherited by all subclasses and represent shared behaviour that does not need to vary. A common pattern is to declare the algorithm template as a concrete final method and the variable steps as abstract.

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

The subclass must itself be declared abstract. It can then be extended by another class that implements the remaining abstract methods. A class cannot be concrete (instantiable) unless all abstract methods in its inheritance chain are implemented.

Can an abstract class have a static method?

Yes. Static methods in an abstract class work exactly like static methods in any class — they belong to the class, not to objects, and are called on the class name. Static methods cannot be abstract because abstraction requires overriding, and static methods are not polymorphic.

What is the difference between abstract method and abstract class?

An abstract method is a method declared with abstract that has no body — subclasses must implement it. An abstract class is a class declared with abstract that cannot be instantiated — it can contain abstract methods. An abstract method can only exist inside an abstract class or interface. An abstract class can exist with zero abstract methods (though this is uncommon) — it would still prevent instantiation but all subclass obligation would come from the concrete methods.

Summary

Abstract classes occupy the middle ground between concrete classes and interfaces — they provide structure through fields and constructors, share implementation through concrete methods, and impose obligations through abstract methods. The Template Method Pattern is their most powerful application: a fixed algorithm in a final concrete method, with variable steps delegated to abstract methods that each subclass fills in independently.

The decision between abstract class and interface comes down to what subclasses need to share. Shared state or shared constructor logic point to an abstract class. A pure contract with no shared implementation points to an interface. When both are needed — shared logic and multiple type flexibility — an abstract class can implement interfaces, leaving abstract methods for concrete subclasses to complete.

For interviews, be ready to explain the difference between abstract class and interface with concrete trade-offs, demonstrate the Template Method Pattern with real code, explain what abstract methods and hook methods are and when to use each, and describe why abstract classes can have constructors. These points appear in both service-based recall and product-based design discussions.

What to Read Next

Practice Programs

Put this topic into practice — each program has full code, output and a step-by-step explanation you can run online.

Browse all Java programs →