Abstract Class vs Interface in Java (When to Use Each)
backend
9 min read
Abstract class or interface? Most Java devs get this wrong. Here's a clear breakdown with a side-by-side comparison table, code examples, and a simple decision rule.

Published By: Nelson Djalo | Date: April 5, 2026
An abstract class says "these things are related and share some behavior." An interface says "these things can do this action, regardless of what they are."
That's really it. Everything else flows from this idea.
A Dog and a Cat both extend Animal because they are animals. But a Dog, a Robot, and a Drone might all implement Trackable because they can be tracked - even though they have nothing else in common.
If you've been writing Java for a while and still second-guess this decision, you're not alone. Let's make it click with real code.
Let's say you're building a payment system. You have credit cards, PayPal, and bank transfers. They all share some common state and behavior:
public abstract class PaymentMethod {
private String ownerName;
private boolean verified;
public PaymentMethod(String ownerName) {
this.ownerName = ownerName;
this.verified = false;
}
// Every payment method must implement this differently
public abstract void processPayment(double amount);
// Shared behavior - same for all payment methods
public void verify() {
// common verification logic
this.verified = true;
System.out.println(ownerName + "'s payment method verified.");
}
public boolean isVerified() {
return verified;
}
public String getOwnerName() {
return ownerName;
}
}
Now a concrete class extends it:
public class CreditCard extends PaymentMethod {
private String cardNumber;
public CreditCard(String ownerName, String cardNumber) {
super(ownerName);
this.cardNumber = cardNumber;
}
@Override
public void processPayment(double amount) {
System.out.println("Charging $" + amount + " to card " + cardNumber);
}
}
Notice what the abstract class gives us: a constructor, private fields, a mix of abstract and concrete methods. Subclasses inherit real state and behavior. This is the kind of thing interfaces simply cannot do in the same way.
If you're just getting started with Java and want to build this kind of thing from scratch, the Java for Beginners course walks you through OOP step by step.
Now let's say some things in your system need to be refundable. Not just payment methods - maybe subscriptions, orders, and gift cards too. They have nothing else in common. Perfect use case for an interface:
public interface Refundable {
void refund(double amount);
boolean isEligibleForRefund();
}
Any class can implement it:
public class Order implements Refundable {
private double total;
private boolean shipped;
public Order(double total) {
this.total = total;
this.shipped = false;
}
@Override
public void refund(double amount) {
System.out.println("Refunding $" + amount + " for order.");
}
@Override
public boolean isEligibleForRefund() {
return !shipped;
}
}
The key thing: Order doesn't extend some "refundable parent." It just promises it can do refund-related things. That's a contract, not an inheritance relationship.
Here's everything in one place:
| Feature | Abstract Class | Interface |
|---|---|---|
| Instantiation | Cannot be instantiated directly | Cannot be instantiated directly |
| Constructors | Yes, can have constructors | No constructors |
| Fields | Instance fields (any type, mutable state) | Only public static final constants |
| Methods | Abstract + concrete methods | Abstract, default, static, private (Java 9+) |
| Inheritance | Single inheritance only (extends one class) | Multiple inheritance (implements many) |
| Access modifiers | All access modifiers allowed | Methods are public by default |
| Use case | Shared state and behavior among related classes | Defining capabilities across unrelated classes |
The biggest practical difference? A class can implement 10 interfaces but can only extend one abstract class. That constraint alone drives most design decisions.
This is where it gets interesting. In real projects you almost always combine both. Here's our CreditCard extending an abstract class and implementing an interface:
public class CreditCard extends PaymentMethod implements Refundable {
private String cardNumber;
public CreditCard(String ownerName, String cardNumber) {
super(ownerName);
this.cardNumber = cardNumber;
}
@Override
public void processPayment(double amount) {
System.out.println("Charging $" + amount + " to card " + cardNumber);
}
@Override
public void refund(double amount) {
System.out.println("Refunding $" + amount + " to card " + cardNumber);
}
@Override
public boolean isEligibleForRefund() {
return isVerified();
}
}
CreditCard is a PaymentMethod (abstract class) and can do refunds (interface). Both relationships are clear and they serve different purposes.
This pattern shows up everywhere in professional Java code. The Java Master Class dives deep into designing these kinds of hierarchies for real-world applications.
Before Java 8, interfaces could only have abstract methods. That changed with default methods:
public interface Refundable {
void refund(double amount);
boolean isEligibleForRefund();
// Default method - provides a body, but can be overridden
default String getRefundPolicy() {
return "Standard 30-day refund policy.";
}
}
Any class implementing Refundable gets getRefundPolicy() for free. It can override it if needed, but it doesn't have to.
This blurred the line between abstract classes and interfaces. So here's the updated rule of thumb:
Default methods were added mainly for backward compatibility (think List.forEach() in the Collections API). They're not a replacement for abstract classes. If your "interface" needs a constructor and five fields, it's an abstract class in disguise.
Java 17 introduced sealed types, and they work with both classes and interfaces. A sealed interface restricts which classes can implement it:
public sealed interface Shape
permits Circle, Rectangle, Triangle {
double area();
}
public record Circle(double radius) implements Shape {
@Override
public double area() {
return Math.PI * radius * radius;
}
}
public record Rectangle(double width, double height) implements Shape {
@Override
public double area() {
return width * height;
}
}
public record Triangle(double base, double height) implements Shape {
@Override
public double area() {
return 0.5 * base * height;
}
}
Why does this matter? Two big reasons:
switch exhaustive - no need for a default case.Shape interface and break your assumptions.public double describe(Shape shape) {
return switch (shape) {
case Circle c -> "Circle with radius " + c.radius();
case Rectangle r -> "Rectangle " + r.width() + "x" + r.height();
case Triangle t -> "Triangle with base " + t.base();
// No default needed - compiler knows this is exhaustive
};
}
Sealed interfaces pair beautifully with records and pattern matching. If you're building with Java 17+, this is a pattern you'll want in your toolkit. For a deeper look at how inheritance works under the hood, check out Inheritance in Java.
When you're staring at your code wondering which one to pick, ask yourself two questions:
1. Do the classes share state (fields) or need a constructor? Yes -> Abstract class.
2. Are you defining a capability that unrelated classes might need? Yes -> Interface.
If both are true (which happens often), use both. Abstract class for the shared foundation, interface for the cross-cutting capabilities.
Here's one more angle: if you're designing something and you think "I might need to add a second parent later" - that's an interface. Java's single inheritance means you get one shot with extends, so save it for the relationship that truly represents an "is-a" hierarchy.
For a bigger picture of how these patterns fit into building real applications, the Spring Boot Roadmap shows how interfaces drive dependency injection and inversion of control in Spring.
Yes. This is actually common. An abstract class can implement an interface and provide default implementations for some (or all) of the interface's methods. Subclasses then fill in the rest or override as needed.
public abstract class AbstractOrder implements Refundable {
// Implement one method, leave the other abstract
@Override
public boolean isEligibleForRefund() {
return true;
}
}
Use an abstract class when your subclasses need to share actual state (instance variables) and you want to enforce initialization through constructors. If you just need to define a contract without shared state, an interface is the better choice.
Yes, and an interface can extend multiple interfaces. This is how you compose contracts:
public interface Purchasable extends Refundable, Shippable {
void purchase();
}
Any class implementing Purchasable must implement all methods from Refundable, Shippable, and Purchasable.
The compiler forces you to override the conflicting method. You can choose which default to call using InterfaceName.super.methodName(), or you can write your own implementation entirely.
public class MyClass implements InterfaceA, InterfaceB {
@Override
public void conflictingMethod() {
InterfaceA.super.conflictingMethod(); // pick one
}
}
No. In modern JVMs, the performance difference is negligible. The JIT compiler optimizes virtual method dispatch for both. Pick based on design, not performance. You'll never notice a difference in real applications.
Abstract classes share state and behavior among related types. Interfaces define capabilities that any class can adopt. Use abstract classes for "is-a" relationships with shared fields, and interfaces for "can-do" contracts across unrelated classes. When in doubt, start with an interface - you can always extract an abstract class later if shared state emerges.
Want to put this into practice? The Java Master Class has hands-on projects where you'll design class hierarchies, implement interfaces, and build real applications from scratch.

Skip the generic recommendations. These 9 books changed how I write code, lead teams, and think about systems - from Clean Code to books most devs haven't heard of.

The exact skills, tools, and learning order to go from zero to hired as a Java full stack developer. Covers Spring Boot, React, databases, Docker, and what employers actually look for.

A step-by-step backend developer roadmap for 2026 focused on Java and Spring Boot. Covers everything from programming basics to system design, databases, APIs, DevOps, and landing your first job.
Join thousands of developers mastering in-demand skills with Amigoscode. Try it free today.