Java Tutorial
🔍

Java Abstract Class vs Interface

Java Abstract Class vs Interface

Every Java developer reaches a point in system design where they must choose: should this shared contract be an abstract class or an interface? Both can declare methods that subclasses must implement. Both cannot be instantiated directly. Both establish type hierarchies that polymorphism depends on.

The similarity ends there. An abstract class can hold state, run constructor logic, and provide concrete implementation shared across all subclasses. An interface defines a pure contract — what an object can do, with no assumption about what it is. Java 8 blurred the line by adding default and static methods to interfaces, but the fundamental distinction remains. Understanding it changes how you design every class hierarchy you write.

The Core Distinction in One Sentence

An abstract class defines what a thing is — a partial implementation that subclasses complete. An interface defines what a thing can do — a capability contract that any class can adopt regardless of its place in the hierarchy.

Abstract Class — IS-A relationship:

  Vehicle (abstract)
      |
  has: fuelType, engineSize fields
  has: refuel() concrete method
  has: startEngine() abstract method
      |
   +--+--+
   |     |
 Car   Truck
  |       |
implements implements
startEngine() startEngine()


Interface — CAN-DO relationship:

  Serializable     Printable     Comparable
      |                |              |
  Any class can    Any class can  Any class can
  implement        implement      implement
  regardless of    regardless of  regardless of
  its hierarchy    its hierarchy  its hierarchy

A Car IS-A Vehicle — it makes sense to extend the abstract class. A Car CAN-DO Serializable — it makes sense to implement the interface. These are different kinds of relationships, and mixing them up produces designs that are painful to maintain.

Abstract Class — When State and Shared Logic Matter

An abstract class is the right tool when multiple related classes share concrete implementation — fields, constructor logic, or non-trivial methods — and differ only in specific behaviours.

// File: ReportGenerator.java public abstract class ReportGenerator { // Shared state — all reports have these private final String reportTitle; private final String preparedBy; private final java.time.LocalDate generatedOn; // Constructor — runs for every subclass via super() protected ReportGenerator(String reportTitle, String preparedBy) { if (reportTitle == null || reportTitle.isBlank()) { throw new IllegalArgumentException("Report title cannot be empty."); } this.reportTitle = reportTitle; this.preparedBy = preparedBy; this.generatedOn = java.time.LocalDate.now(); } public String getReportTitle() { return reportTitle; } public String getPreparedBy() { return preparedBy; } // Concrete method — shared header logic, no need to repeat in each subclass protected String buildHeader() { return "=".repeat(50) + "\n Report : " + reportTitle + "\n Prepared : " + preparedBy + "\n Date : " + generatedOn + "\n" + "=".repeat(50); } // Template method — controls the report generation flow public final String generate() { return buildHeader() + "\n" + buildBody() + "\n" + buildFooter(); } // Abstract — each report type provides its own body protected abstract String buildBody(); // Hook — subclasses may override if they need a custom footer protected String buildFooter() { return "\n--- End of " + reportTitle + " ---"; } }
// File: SalesReport.java public class SalesReport extends ReportGenerator { private final double totalRevenue; private final int totalOrders; private final String region; public SalesReport(String preparedBy, double totalRevenue, int totalOrders, String region) { super("Monthly Sales Report", preparedBy); this.totalRevenue = totalRevenue; this.totalOrders = totalOrders; this.region = region; } @Override protected String buildBody() { double avgOrder = totalOrders > 0 ? totalRevenue / totalOrders : 0; return " Region : " + region + "\n Total Orders : " + totalOrders + "\n Total Revenue : Rs." + String.format("%.2f", totalRevenue) + "\n Avg Order : Rs." + String.format("%.2f", avgOrder); } }
// File: InventoryReport.java public class InventoryReport extends ReportGenerator { private final int totalSkus; private final int lowStockItems; private final double inventoryValue; public InventoryReport(String preparedBy, int totalSkus, int lowStockItems, double inventoryValue) { super("Inventory Summary Report", preparedBy); this.totalSkus = totalSkus; this.lowStockItems = lowStockItems; this.inventoryValue = inventoryValue; } @Override protected String buildBody() { return " Total SKUs : " + totalSkus + "\n Low Stock : " + lowStockItems + " items" + "\n Inventory Val : Rs." + String.format("%.2f", inventoryValue); } }
// File: AbstractClassDemo.java public class AbstractClassDemo { public static void main(String[] args) { ReportGenerator sales = new SalesReport( "Ananya Krishnan", 485000.0, 320, "South India" ); ReportGenerator inventory = new InventoryReport( "Rohan Desai", 1240, 38, 2850000.0 ); System.out.println(sales.generate()); System.out.println(); System.out.println(inventory.generate()); } }
Output:
==================================================
  Report   : Monthly Sales Report
  Prepared : Ananya Krishnan
  Date     : 2024-01-15
==================================================
  Region        : South India
  Total Orders  : 320
  Total Revenue : Rs.485000.00
  Avg Order     : Rs.1515.63

--- End of Monthly Sales Report ---

==================================================
  Report   : Inventory Summary Report
  Prepared : Rohan Desai
  Date     : 2024-01-15
==================================================
  Total SKUs    : 1240
  Low Stock     : 38 items
  Inventory Val : Rs.2850000.00

--- End of Inventory Summary Report ---

ReportGenerator owns the fields, the constructor validation, the header format, and the generate() flow. Subclasses provide only what genuinely varies: the body content. Without the abstract class, every report would duplicate the header building, the date tracking, and the constructor validation.

Interface — When Capability Matters More Than Lineage

An interface is the right tool when unrelated classes need to share a contract but share no implementation. A PaymentProcessor, a FileUploader, and a CacheWarmer might all need to be Retryable — but they extend different parent classes and share no concrete logic.

// File: Retryable.java public interface Retryable { // Abstract — implementing class provides the retry logic int getMaxRetries(); long getRetryDelayMs(); // Default method — shared retry outcome description, overridable default String describeRetryPolicy() { return "Max retries: " + getMaxRetries() + ", Delay: " + getRetryDelayMs() + "ms"; } }
// File: Validatable.java public interface Validatable { boolean isValid(); String getValidationMessage(); }
// File: PaymentRequest.java // PaymentRequest is both Retryable and Validatable — two unrelated capabilities public class PaymentRequest implements Retryable, Validatable { private final String orderId; private final double amount; private final String paymentMethod; public PaymentRequest(String orderId, double amount, String paymentMethod) { this.orderId = orderId; this.amount = amount; this.paymentMethod = paymentMethod; } @Override public int getMaxRetries() { return 3; } @Override public long getRetryDelayMs() { return 1000L; } @Override public boolean isValid() { return amount > 0 && orderId != null && !orderId.isBlank() && paymentMethod != null && !paymentMethod.isBlank(); } @Override public String getValidationMessage() { if (amount <= 0) return "Amount must be positive."; if (orderId == null) return "Order ID is required."; return "Valid payment request."; } public String getOrderId() { return orderId; } public double getAmount() { return amount; } }
// File: InterfaceDemo.java public class InterfaceDemo { // Methods accept interface types — work with any implementing class public static void processWithRetry(Retryable retryable) { System.out.println("Processing with policy: " + retryable.describeRetryPolicy()); } public static void validate(Validatable validatable) { boolean valid = validatable.isValid(); System.out.println("Valid: " + valid + " — " + validatable.getValidationMessage()); } public static void main(String[] args) { PaymentRequest req1 = new PaymentRequest("ORD-501", 2499.0, "UPI"); PaymentRequest req2 = new PaymentRequest("", -50.0, ""); processWithRetry(req1); validate(req1); System.out.println(); processWithRetry(req2); validate(req2); } }
Output:
Processing with policy: Max retries: 3, Delay: 1000ms
Valid: true — Valid payment request.

Processing with policy: Max retries: 3, Delay: 1000ms
Valid: false — Amount must be positive.

PaymentRequest implements two completely unrelated interfaces. It does not extend any parent class. Both capabilities are tested through their respective interface types — no knowledge of PaymentRequest needed. This is the flexibility that interfaces provide: any class, regardless of its hierarchy, can adopt any capability.

Side-by-Side Comparison — Abstract Class vs Interface

AspectAbstract ClassInterface
Keywordabstract classinterface
Used withextends (one only)implements (many)
Multiple inheritanceNo — single extends onlyYes — a class can implements many
Instance fieldsYes — any typeNo — only public static final constants
ConstructorsYes — called via super()No
Abstract methodsYes — abstract keywordYes — all methods abstract by default
Concrete methodsYes — any visibilitydefault and static only (Java 8+)
Private methodsYesYes — private allowed in interface (Java 9+)
Access modifiers on methodsAny — public, protected, packagepublic by default
State (mutable data)YesNo
IS-A relationshipYes — strong type hierarchyYes — capability-based type
When to useShared state + shared implementationPure contract, multiple capabilities
InstantiationNoNo
@OverrideRecommendedRecommended

Java 8+ Default Methods — The Overlap Zone

Java 8 added default methods to interfaces, giving them concrete implementations. This narrowed the gap between abstract classes and interfaces and created genuine confusion about when to choose which.

// File: Loggable.java public interface Loggable { // Abstract — each implementing class provides its own logger name String getLoggerName(); // Default — shared logging implementation any class can use default void logInfo(String message) { System.out.println("[INFO][" + getLoggerName() + "] " + message); } default void logError(String message, Exception e) { System.out.println("[ERROR][" + getLoggerName() + "] " + message + " — " + e.getMessage()); } // Static — utility method on the interface itself static String formatMessage(String level, String name, String msg) { return "[" + level + "][" + name + "] " + msg; } }
// File: OrderService.java public class OrderService implements Loggable { private final String serviceName = "OrderService"; @Override public String getLoggerName() { return serviceName; } public void processOrder(String orderId) { logInfo("Processing order: " + orderId); // processing logic logInfo("Order processed: " + orderId); } }
// File: DefaultMethodDemo.java public class DefaultMethodDemo { public static void main(String[] args) { OrderService service = new OrderService(); service.processOrder("ORD-7821"); // Static interface method — called on interface name System.out.println(Loggable.formatMessage("WARN", "Gateway", "Timeout detected")); } }
Output:
[INFO][OrderService] Processing order: ORD-7821
[INFO][OrderService] Order processed: ORD-7821
[WARN][Gateway] Timeout detected

Default methods let interfaces provide shared behaviour without forcing every implementing class to write it. But they cannot access instance fields — they can only call abstract methods declared in the same interface. When the shared behaviour needs to read or write instance state, only an abstract class can provide that.

Abstract Class vs Interface After Java 8 — What Still Differs

CapabilityAbstract ClassInterface (Java 8+)
Instance fields (mutable state)YesNo — constants only
ConstructorYesNo
Access instance state in methodsYesNo — default methods cannot access fields
protected methodsYesNo — interface methods are public
Multiple inheritanceNoYes
Can extend a classYesNo
Partial implementation patternYesPartial via default methods
private helper methodsYesYes (Java 9+) — but only for default method helpers
Intended relationshipIS-A (type lineage)CAN-DO (capability)

Even after Java 8, the decisive differences are: instance state and multiple inheritance. If the shared logic needs to read or write fields on the object, use an abstract class. If a class needs to satisfy multiple independent contracts simultaneously, use interfaces.

Choosing Between Them — Decision Guide

START HERE
     |
     v
Does the shared code need to access
or modify instance-level state (fields)?
     |
  Yes |                    No
     |                     |
     v                     v
Use ABSTRACT CLASS    Does a class need to
                      implement multiple
                      contracts simultaneously?
                           |
                        Yes |           No
                           |            |
                           v            v
                     Use INTERFACE   Does the hierarchy
                                     have a clear IS-A
                                     relationship?
                                          |
                                       Yes |         No
                                          |           |
                                          v           v
                                    Either works — INTERFACE
                                    prefer INTERFACE (more flexible)

In practice, the most common pattern in modern Java combines both: an interface defines the contract, an abstract class provides a partial implementation, and concrete classes extend the abstract class. Java's AbstractList (abstract class) implements List (interface) is the canonical example.

Basic Example

Honkable is a pure interface — a capability any vehicle can have. Vehicle is an abstract class — it holds the shared model field and constructor, and a concrete showInfo() method, while leaving wheelCount() abstract for each subclass to fill in.

public class Main { public static void main(String[] args) { Vehicle car = new Car("Sedan"); Vehicle bike = new Bike("Sports"); car.showInfo(); bike.showInfo(); } } interface Honkable { void honk(); } abstract class Vehicle implements Honkable { private final String model; Vehicle(String model) { this.model = model; } abstract int wheelCount(); void showInfo() { System.out.println(model + " has " + wheelCount() + " wheels"); honk(); } } class Car extends Vehicle { Car(String model) { super(model); } @Override int wheelCount() { return 4; } @Override public void honk() { System.out.println("Car: Beep beep!"); } } class Bike extends Vehicle { Bike(String model) { super(model); } @Override int wheelCount() { return 2; } @Override public void honk() { System.out.println("Bike: Tring tring!"); } }

Output

Sedan has 4 wheels
Car: Beep beep!
Sports has 2 wheels
Bike: Tring tring!

How it works:

  1. ›Honkable is a pure contract — no fields, no shared code — exactly what an interface is for.
  2. ›Vehicle is an abstract class because it needs to hold state (model) and constructor logic (Vehicle(String model)) that every subclass shares — something an interface cannot do.
  3. ›Car and Bike each complete the abstract wheelCount() method and their own honk() implementation, while inheriting showInfo() unchanged from Vehicle.
  4. ›This mirrors the common real pattern: an interface defines a capability, an abstract class supplies shared state and behaviour, and concrete classes fill in what genuinely differs.

Best Practices

Start with an interface; move to an abstract class only when state or shared logic demands it. Interfaces impose fewer constraints — a class can implement many but extend only one. Beginning with an interface keeps future design options open. If you later discover that three implementations all repeat the same field or the same validation logic, extract it into an abstract class that implements the interface.

Combine interface and abstract class for maximum flexibility. The standard library pattern — interface defines the contract, abstract class provides partial implementation, concrete classes complete it — gives callers the flexibility of the interface type while giving implementors the convenience of the shared base. Collection → AbstractCollection → ArrayList is the canonical Java example.

Never add methods to an existing interface without a default implementation. Adding an abstract method to a widely-used interface breaks every class that implements it. If the new method cannot be made default, create a new interface. This is why Java 8 introduced default methods — not to give interfaces state, but to allow them to evolve without breaking existing code.

Use abstract classes for template method pattern; interfaces for strategy pattern. When the algorithm skeleton is fixed but individual steps vary, an abstract class with a final template method is the right structure. When the entire algorithm is swappable — and many implementations may coexist at runtime — an interface is the right structure.

Common Mistakes

Mistake 1 — Putting State Into an Interface

// Compile error — interface fields are implicitly public static final public interface OrderTracker { int orderCount = 0; // this is a constant, not mutable state void incrementCount(); // implementing class cannot store count here int getCount(); // nothing for this to read } // What you actually need public abstract class OrderTracker { private int orderCount = 0; // real mutable state public void incrementCount() { orderCount++; } public int getCount() { return orderCount; } }

Every field in an interface is implicitly public static final. You cannot use an interface to share mutable state across instances. When shared mutable state is part of the design, an abstract class is the only option.

Mistake 2 — Using Abstract Class Where Multiple Inheritance Is Needed

// Problem — a class cannot extend two abstract classes public abstract class Flyable { public abstract void fly(); } public abstract class Swimmable { public abstract void swim(); } public class Duck extends Flyable, Swimmable { // compile error — two extends } // Fix — make them interfaces public interface Flyable { void fly(); } public interface Swimmable { void swim(); } public class Duck implements Flyable, Swimmable { @Override public void fly() { System.out.println("Duck flies."); } @Override public void swim() { System.out.println("Duck swims."); } }

When a class needs to represent multiple independent capabilities from unrelated lineages, interfaces are the only mechanism Java provides. Reaching for abstract classes in this situation forces a single-parent constraint that the design cannot accommodate.

Mistake 3 — Treating Default Methods as a Replacement for Abstract Class State

public interface StatefulCounter { // This does NOT work — default methods cannot access instance fields // There is no "count" field to read in an interface default int increment() { // count++; // compile error — no such field return -1; // meaningless without state } }

Default methods in interfaces share code — they cannot share state. A default method can only call other methods declared in the same interface. It cannot read or write instance fields because interfaces have no instance fields. When the shared behaviour requires reading or mutating object state, an abstract class is the correct choice regardless of what Java 8 added.

Interview Questions

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

An abstract class can hold instance fields, constructors, and both abstract and concrete methods with any access modifier. A class can extend only one abstract class. An interface defines abstract methods with no instance state or constructors — since Java 8 it can have default and static methods. A class can implement multiple interfaces simultaneously. Use an abstract class when related classes share mutable state or common implementation. Use an interface for pure contracts, capability definitions, or when a class needs to satisfy multiple independent contracts.

Q2. 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 members of the abstract class, to be implemented by its concrete subclasses. This is a common layered design: the interface defines the full contract, the abstract class provides shared partial implementation, and concrete classes complete the remaining methods.

Q3. Why can a class implement multiple interfaces but extend only one class?

Java avoids the diamond problem of C++ — when two parent classes define the same method, the compiler cannot determine which version to inherit. Interfaces solved this in Java 8+ by requiring the implementing class to explicitly resolve any default method conflicts. Classes are not required to implement methods inherited from abstract parents — the diamond problem is real for state and concrete methods. Multiple interface implementation is safe because interfaces share no instance state.

Q4. When would you choose an abstract class over an interface even in modern Java?

Choose an abstract class when the shared code needs to access or modify instance fields — default methods in interfaces cannot do this. Choose it when constructor logic needs to run and be reusable across subclasses — interfaces have no constructors. Choose it when protected or package-private methods need to be shared — interface methods must be public. Choose it for the template method pattern where the algorithm skeleton is fixed and must not be overridden by implementors.

Q5. What changed for interfaces in Java 8 and does it make abstract classes obsolete?

Java 8 added default methods — concrete implementations in interfaces — and static methods. This allowed interfaces to evolve without breaking existing implementors and to provide shared utility behaviour. However, interfaces still cannot hold instance state, have constructors, or declare non-public methods. Abstract classes are not obsolete — they remain the only option whenever shared mutable state or constructor chaining is needed. The typical modern pattern uses both: an interface for the contract and an abstract class for the shared partial implementation.

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

No. Interfaces can only extend other interfaces — they cannot extend classes, abstract or otherwise. An abstract class can implement an interface. A concrete class can extend an abstract class and implement interfaces simultaneously. The hierarchy is one-directional: interfaces are extended by interfaces; classes extend classes (at most one) and implement interfaces (many).

FAQs

Can an abstract class have a constructor in Java?

Yes. Abstract classes can have constructors, and they serve an important purpose: initialising shared fields that subclasses inherit. The constructor is called via super() from each subclass constructor. It cannot be called directly since abstract classes cannot be instantiated with new, but it runs every time a concrete subclass object is created.

Can an interface have private methods in Java?

Yes, since Java 9. Private methods in interfaces are helper methods that default methods in the same interface can call internally. They reduce code duplication within the interface body without exposing the helper logic as part of the public contract. They are invisible to implementing classes — only the default methods in the same interface can call them.

Should you always prefer interfaces over abstract classes?

Interfaces are generally preferred when there is no shared state because they impose fewer restrictions. A class that implements an interface still has its extends slot free for a future hierarchy. However, when shared fields, constructor logic, or protected helper methods are genuinely needed, forcing everything into an interface produces clunky designs with duplicated code. The right choice depends on whether the design needs shared state — not on a blanket preference.

What happens when two interfaces a class implements have the same default method?

The implementing class must override the conflicting default method to resolve the ambiguity — the compiler enforces this with an error. Inside the override, the class can call a specific interface's version using InterfaceA.super.method(), or provide an entirely new implementation. This is Java's explicit resolution of the diamond problem for default methods.

Can you instantiate an abstract class using an anonymous class?

Yes. An anonymous class can extend an abstract class and provide implementations for all abstract methods at the point of instantiation. The result is a nameless concrete subclass that exists only at that call site. This pattern appears frequently in older Java code — lambdas and method references have largely replaced it for single-method cases in modern Java.

Summary

Abstract classes and interfaces solve different design problems. Abstract classes are for IS-A relationships with shared state and shared implementation — when multiple related types share fields, constructor logic, or non-trivial methods, the abstract class holds what they have in common. Interfaces are for CAN-DO relationships — when any class, regardless of its lineage, should be able to declare a capability.

Java 8 narrowed the gap with default methods, but the decisive differences remain: instance state, constructors, and access modifiers. When the shared logic needs to read or write object fields, an abstract class is the only correct choice. When a class must satisfy multiple independent contracts simultaneously, interfaces are the only mechanism available.

The most common production pattern combines both — an interface establishes the contract, an abstract class provides partial implementation, and concrete classes complete the work. This gives callers the flexibility of the interface type while sparing implementors from repeating identical boilerplate. Recognising which layer belongs where is a skill that separates junior code from senior design.

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 →