Java Abstract Classes
Updated 9 min read Aimerscrew
A Java abstract class is a class that is deliberately unfinished. It describes what a group of related classes have in common, leaves a few "blanks" for each subclass to fill in, and refuses to be turned into an object on its own. In this lesson, you'll learn how to write abstract classes and abstract methods, why Java blocks you from creating them with new, and how they let you share most of the code while still forcing each subclass to do its part.
🌍 Real-life example
A tax form is a template. Everyone fills out the same form with the same boxes, the same order and the same rules, but each person writes in their own income. Nobody hands in a blank form; the template only becomes useful once someone completes it. An abstract class is that form: the shared structure is printed, and the subclass fills in the blanks.
What is an abstract class in Java?
An abstract class is a class declared with the abstract keyword. It can contain fields, constructors and normal methods like any other class, plus abstract methods: methods with a signature but no body. Because it is incomplete, you cannot create an object of an abstract class. You can only create objects of its subclasses.
In the polymorphism lesson, our Shape class had an area() method that returned 0, just so subclasses had something to override. That was a fake answer. An abstract method says honestly: "every shape has an area, but there is no general formula, so each subclass must provide one".
How do you declare an abstract method?
An abstract method has the abstract keyword, a return type, a name and parameters, and then a semicolon instead of a body:
abstract class Payment {
abstract boolean process(double amount); // no { }, just ;
}
Two rules come with it:
- If a class has even one abstract method, the class itself must be declared
abstract. - A normal (non-abstract) subclass must override every abstract method it inherits. Otherwise it won't compile.
A class that is not abstract is called a concrete class. Every class you wrote before this lesson was concrete.
A complete example: payments at checkout
An online store accepts cards, PayPal and gift cards. The checkout steps are always the same: show the amount, process the payment, print the result. Only the "process" step differs. That's a perfect job for an abstract class:
public class Checkout {
public static void main(String[] args) {
Payment[] payments = {
new CardPayment("4242"),
new PayPalPayment("sam@example.com"),
new GiftCardPayment(20.00)
};
for (Payment p : payments) {
p.checkout(35.50);
}
}
}
abstract class Payment {
// Concrete method: the same steps for every payment
void checkout(double amount) {
System.out.printf("--- Paying $%.2f ---%n", amount);
boolean ok = process(amount);
System.out.println(ok ? "Payment approved" : "Payment declined");
}
// Abstract method: each payment type fills this in
abstract boolean process(double amount);
}
class CardPayment extends Payment {
private String lastFour;
CardPayment(String lastFour) {
this.lastFour = lastFour;
}
@Override
boolean process(double amount) {
System.out.println("Charging card ending in " + lastFour);
return true;
}
}
class PayPalPayment extends Payment {
private String email;
PayPalPayment(String email) {
this.email = email;
}
@Override
boolean process(double amount) {
System.out.println("Redirecting to PayPal for " + email);
return true;
}
}
class GiftCardPayment extends Payment {
private double balance;
GiftCardPayment(double balance) {
this.balance = balance;
}
@Override
boolean process(double amount) {
System.out.printf("Gift card balance: $%.2f%n", balance);
return balance >= amount;
}
}
--- Paying $35.50 ---
Charging card ending in 4242
Payment approved
--- Paying $35.50 ---
Redirecting to PayPal for sam@example.com
Payment approved
--- Paying $35.50 ---
Gift card balance: $20.00
Payment declined
The checkout() method is written once, in the abstract class. In the middle, it calls process(), a method it doesn't know how to do. Thanks to dynamic dispatch, each object's own process() runs at that moment.
process() step.This design is often called the template method pattern: a concrete method in the parent lays out the steps (the template), and abstract methods are the steps subclasses supply.
Why can't you create an object of an abstract class?
Imagine calling new Payment() and then checkout(). When the code reached process(amount), there would be no body to run. Java prevents that problem before the program even starts:
Payment p = new Payment(); // error: Payment is abstract; cannot be instantiated
"Instantiate" just means "create an instance (an object) of". However, you can use the abstract class as a reference type, which is exactly what Payment[] payments and Payment p in the loop do. The variable is of type Payment; the objects are always concrete subclasses.
Abstract classes can have fields and constructors
An abstract class is still a real class. It can store data, keep it private, and have a constructor that subclasses call with super(...):
public class FloorPlan {
public static void main(String[] args) {
Room[] rooms = { new SquareRoom("Kitchen", 4), new RoundRoom("Reading nook", 1.5) };
double total = 0;
for (Room r : rooms) {
r.describe();
total += r.area();
}
System.out.printf("Total floor area: %.2f m2%n", total);
}
}
abstract class Room {
private String name;
Room(String name) { // abstract classes can have constructors
this.name = name;
}
abstract double area(); // no body, ends with a semicolon
void describe() {
System.out.printf("%s: %.2f m2%n", name, area());
}
}
class SquareRoom extends Room {
private double side;
SquareRoom(String name, double side) {
super(name);
this.side = side;
}
@Override
double area() {
return side * side;
}
}
class RoundRoom extends Room {
private double radius;
RoundRoom(String name, double radius) {
super(name);
this.radius = radius;
}
@Override
double area() {
return Math.PI * radius * radius;
}
}
Kitchen: 16.00 m2
Reading nook: 7.07 m2
Total floor area: 23.07 m2
The constructor of Room never runs on its own; it runs as the first step of building a SquareRoom or a RoundRoom.
📝 Note
An abstract class doesn't need any abstract methods. Marking a class abstract is enough to stop people from creating it directly. That's handy for a base class that only makes sense as a parent.
Abstract class vs concrete class
| Abstract class | Concrete class | |
|---|---|---|
| Keyword | abstract class Payment | class CardPayment |
Create objects with new? | No | Yes |
| Abstract methods? | Allowed (zero or more) | Not allowed |
| Normal methods, fields, constructors? | Yes | Yes |
| Use as a variable or array type? | Yes | Yes |
| Typical role | A shared base that is "not finished" | A real thing you can use |
A subclass of an abstract class can itself be abstract. Then it may leave some abstract methods for its subclasses to finish. Only the first concrete class in the chain must have them all.
When should you use an abstract class?
Reach for an abstract class when all of these are true:
- The classes truly have an "is-a" relationship (a CardPayment is a Payment).
- They share real code or data, not just method names.
- Some steps must be different in each subclass, and you want the compiler to make sure nobody forgets them.
If the classes share only method names and no code, an interface (next lesson) is usually a better fit.
💡 Tip
A quick test: if the base class has a method whose body would be a fake answer (return 0;, an empty body, or printing "..."), make that method abstract.
Common mistakes with abstract classes
1. Forgetting to implement an abstract method
class CashPayment extends Payment { }
// error: CashPayment is not abstract and does not override
// abstract method process(double) in Payment
Either override process(double) or declare CashPayment abstract too.
2. Giving an abstract method a body
abstract double area() { return 0; } fails with abstract methods cannot have a body. An abstract method ends with a semicolon. If you want a default body, make it a normal method instead.
3. Adding an abstract method to a normal class
If a class without the abstract keyword contains an abstract method, the compiler complains that the class "is not abstract and does not override abstract method". Add abstract to the class.
4. Trying to use new on the abstract class
new Room("Hall") gives Room is abstract; cannot be instantiated. Create a concrete subclass, like new SquareRoom("Hall", 3).
Practice exercise
🏋️ Try it yourself
A movie theater sells three kinds of tickets for a base price of $12. Create an abstract Ticket class with a movie name and a seat, abstract methods price() and type(), and a concrete print() method. Create StandardTicket (base price), StudentTicket (half price) and VipTicket (base price plus $8). Sell one of each and print the total.
Show solution
public class BoxOffice {
public static void main(String[] args) {
Ticket[] sold = {
new StandardTicket("Dune", "A12"),
new StudentTicket("Dune", "A13"),
new VipTicket("Dune", "VIP-1")
};
double takings = 0;
for (Ticket t : sold) {
t.print();
takings += t.price();
}
System.out.printf("Total: $%.2f%n", takings);
}
}
abstract class Ticket {
protected static final double BASE_PRICE = 12.00;
private String movie;
private String seat;
Ticket(String movie, String seat) {
this.movie = movie;
this.seat = seat;
}
abstract double price();
abstract String type();
void print() {
System.out.printf("%-8s %s seat %-6s $%.2f%n", type(), movie, seat, price());
}
}
class StandardTicket extends Ticket {
StandardTicket(String movie, String seat) {
super(movie, seat);
}
@Override
double price() {
return BASE_PRICE;
}
@Override
String type() {
return "Standard";
}
}
class StudentTicket extends Ticket {
StudentTicket(String movie, String seat) {
super(movie, seat);
}
@Override
double price() {
return BASE_PRICE * 0.5;
}
@Override
String type() {
return "Student";
}
}
class VipTicket extends Ticket {
VipTicket(String movie, String seat) {
super(movie, seat);
}
@Override
double price() {
return BASE_PRICE + 8.00;
}
@Override
String type() {
return "VIP";
}
}
Standard Dune seat A12 $12.00
Student Dune seat A13 $6.00
VIP Dune seat VIP-1 $20.00
Total: $38.00
What's next?
Abstract classes are great for families of related classes. But what if completely unrelated things, like a phone, an electric car and a laptop, all need the same ability, such as "can be charged"? A class can extend only one parent, so Java offers something more flexible. Next, learn about Java interfaces.
Key takeaways
- An abstract class is declared with the abstract keyword and cannot be instantiated with new.
- An abstract method has no body, just a signature followed by a semicolon.
- Any class with an abstract method must be abstract, and every concrete subclass must override all inherited abstract methods.
- Abstract classes can still have fields, constructors and normal methods, so they can share real code.
- A concrete method can call an abstract one, letting the parent fix the steps while subclasses fill in the details (the template method pattern).
- You can use an abstract class as a variable or array type, and polymorphism picks each subclass's version.
Frequently asked questions
What is an abstract class in Java?
An abstract class is a class marked with the abstract keyword that cannot be used to create objects directly. It can contain both normal methods and abstract methods without a body, which subclasses must implement.
Can an abstract class have a constructor in Java?
Yes. An abstract class can have constructors to set up its fields. They run when a concrete subclass is created, usually through a super(...) call in the subclass constructor.
Can an abstract class have no abstract methods?
Yes. A class can be declared abstract even if all its methods have bodies. This simply prevents anyone from creating objects of that class directly.
What happens if a subclass does not implement an abstract method?
The code does not compile, unless the subclass is also declared abstract. The compiler reports that the class is not abstract and does not override the abstract method.
Can you create an array of an abstract class type?
Yes. An array like Payment[] payments is allowed because it only holds references. Every element must point to an object of a concrete subclass.