Factory Method and Abstract Factory in Java
1. Overview
Factory Method and Abstract Factory are both creational patterns that help you create objects without directly using new on a specific class. But these two patterns solve different problems and are easy to confuse as a newcomer.
In this article, you’ll see the difference through a PaymentMethod example (Factory Method) and a UI creation system by theme (Abstract Factory). After reading, you’ll know when you only need a simple factory, and when you need a whole “factory that produces factories”.
2. What is Factory Method?
Factory Method defines a method to create an object, but lets subclasses decide which specific class will be instantiated. Simply put: you have an interface method create(), and each subclass overrides it to return a different implementation.
2.1. The Problem to Solve
Suppose you have a payment system with multiple types: CreditCardPayment, MomoPayment, BankTransferPayment. If client code directly news them:
public void processOrder(Order order, String type) {
PaymentMethod payment;
if (type.equals("CREDIT_CARD")) {
payment = new CreditCardPayment();
} else if (type.equals("MOMO")) {
payment = new MomoPayment();
} else {
payment = new BankTransferPayment();
}
payment.pay(order.getAmount());
}
Each time you add a new payment type, you have to modify this if-else block everywhere it’s used. This is exactly the code smell that Factory Method solves.
2.2. Applying Factory Method
public interface PaymentMethod {
void pay(BigDecimal amount);
}
public class CreditCardPayment implements PaymentMethod {
@Override
public void pay(BigDecimal amount) {
System.out.println("Pay " + amount + " with credit card");
}
}
public class MomoPayment implements PaymentMethod {
@Override
public void pay(BigDecimal amount) {
System.out.println("Pay " + amount + " via Momo");
}
}
Explanation: PaymentMethod is the common interface, and each implementation handles only its own payment logic. This is the “product” that the factory will create.
Next is the abstract class that contains the Factory Method:
public abstract class PaymentProcessor {
// Factory Method - subclass decides which instance to create
protected abstract PaymentMethod createPaymentMethod();
public void processOrder(Order order) {
PaymentMethod payment = createPaymentMethod();
payment.pay(order.getAmount());
}
}
public class CreditCardProcessor extends PaymentProcessor {
@Override
protected PaymentMethod createPaymentMethod() {
return new CreditCardPayment();
}
}
public class MomoProcessor extends PaymentProcessor {
@Override
protected PaymentMethod createPaymentMethod() {
return new MomoPayment();
}
}
createPaymentMethod()is the Factory Method: it’s an abstract method that each subclass overrides to return the correctPaymentMethodtype it needs.processOrder()is a shared “template” that doesn’t need to know details about which payment type is being created.
Client usage looks like this:
PaymentProcessor processor = new MomoProcessor();
processor.processOrder(new Order(new BigDecimal("150000")));
Pay 150000 via Momo
2.3. Testing Factory Method
class PaymentProcessorTest {
@Test
void shouldCreateCorrectPaymentMethod() {
PaymentProcessor processor = new CreditCardProcessor();
PaymentMethod method = processor.createPaymentMethod();
assertThat(method).isInstanceOf(CreditCardPayment.class);
}
}
Note: To test
createPaymentMethod()(which is protected), either put your test class in the same package asPaymentProcessor, or change the method topublicif you need to test it from outside.
3. What is Abstract Factory?
Abstract Factory provides an interface to create a family of related objects without specifying the concrete classes of each object. Unlike Factory Method which creates only one type of product, Abstract Factory creates multiple products that must “go together”.
3.1. The Problem to Solve
Suppose you’re building UI themes for an application: “Light” theme and “Dark” theme. Each theme needs its own Button and Checkbox, and these two must be styled consistently — you can’t mix LightButton with DarkCheckbox.
public interface Button {
void render();
}
public interface Checkbox {
void render();
}
public class LightButton implements Button {
@Override
public void render() {
System.out.println("Render button with white background, black text");
}
}
public class DarkButton implements Button {
@Override
public void render() {
System.out.println("Render button with dark background, white text");
}
}
public class LightCheckbox implements Checkbox {
@Override
public void render() {
System.out.println("Render checkbox with light gray border");
}
}
public class DarkCheckbox implements Checkbox {
@Override
public void render() {
System.out.println("Render checkbox with white border");
}
}
3.2. Defining Abstract Factory
public interface UiComponentFactory {
Button createButton();
Checkbox createCheckbox();
}
public class LightThemeFactory implements UiComponentFactory {
@Override
public Button createButton() {
return new LightButton();
}
@Override
public Checkbox createCheckbox() {
return new LightCheckbox();
}
}
public class DarkThemeFactory implements UiComponentFactory {
@Override
public Button createButton() {
return new DarkButton();
}
@Override
public Checkbox createCheckbox() {
return new DarkCheckbox();
}
}
UiComponentFactory is the interface that defines the entire family of products (Button + Checkbox). Each implementation (LightThemeFactory, DarkThemeFactory) ensures that the created products always match the same theme.
3.3. Using Abstract Factory in the Client
public class ThemedApplication {
private final UiComponentFactory factory;
public ThemedApplication(UiComponentFactory factory) {
this.factory = factory;
}
public void renderUi() {
Button button = factory.createButton();
Checkbox checkbox = factory.createCheckbox();
button.render();
checkbox.render();
}
}
ThemedApplication app = new ThemedApplication(new DarkThemeFactory());
app.renderUi();
Render button with dark background, white text
Render checkbox with white border
ThemedApplication doesn’t know and doesn’t need to know which concrete classes are being created. It only depends on the UiComponentFactory interface, so changing the theme only requires swapping the injected factory.
3.4. Testing Abstract Factory
class ThemedApplicationTest {
@Test
void shouldCreateConsistentComponentsForDarkTheme() {
UiComponentFactory factory = new DarkThemeFactory();
Button button = factory.createButton();
Checkbox checkbox = factory.createCheckbox();
assertThat(button).isInstanceOf(DarkButton.class);
assertThat(checkbox).isInstanceOf(DarkCheckbox.class);
}
}
This test affirms the most important property of Abstract Factory: products created from the same factory always belong to the same “family”, without mixing Light with Dark.
4. Comparing Factory Method and Abstract Factory
| Aspect | Factory Method | Abstract Factory |
|---|---|---|
| Number of product types | 1 type of product (PaymentMethod) |
Multiple related product types (Button, Checkbox) |
| Mechanism | Override one method in a subclass | Compose multiple factory methods into one interface |
| Relationship | Inheritance | Usually uses composition (inject factory) |
| Goal | Free the client from knowing the concrete class of one object | Ensure consistency among multiple related objects |
A simple way to remember: Abstract Factory is often implemented using a set of Factory Methods. In the example above, createButton() and createCheckbox() in UiComponentFactory are themselves Factory Methods.
4.1. When to Use Factory Method
- You only need to create one type of object, but the creation logic is complex or varies by context.
- You’re working with a class hierarchy and want subclasses to decide what type of child object to use (the template method pattern often pairs with this).
4.2. When to Use Abstract Factory
- You need to create multiple related objects and ensure they’re compatible with each other (same theme, same database vendor, same platform…).
- The system may expand with new “product families” (for example, adding
HighContrastThemeFactory) without modifying client code.
Note: Don’t use Abstract Factory if you only have one product type — it creates unnecessary interface layers. Simple Factory Method is enough.
5. Integration with Spring Boot
In Spring, you rarely write Factory Method/Abstract Factory by hand following the exact GoF structure, because the Spring container already acts as a factory. However, understanding the idea helps you design beans better.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
@Configuration
public class PaymentFactoryConfig {
@Bean
public Map<String, PaymentMethod> paymentMethods() {
return Map.of(
"CREDIT_CARD", new CreditCardPayment(),
"MOMO", new MomoPayment()
);
}
}
@Service
public class PaymentService {
private final Map<String, PaymentMethod> paymentMethods;
public PaymentService(Map<String, PaymentMethod> paymentMethods) {
this.paymentMethods = paymentMethods;
}
public void pay(String type, BigDecimal amount) {
PaymentMethod method = paymentMethods.get(type);
if (method == null) {
throw new IllegalArgumentException("Payment type not supported: " + type);
}
method.pay(amount);
}
}
Here, the Spring container acts as the real “factory”: it pre-creates PaymentMethod beans and clients just look them up by key. This is a practical variant of Factory Method in a Spring environment — you don’t need to write separate XxxProcessor classes for each payment type anymore.
6. Common Mistakes
- Confusing Factory Method with static factory methods (like
Optional.of(),List.of()). A static factory method is just a more concise way to create objects than using a constructor; it’s not related to the GoF pattern’s polymorphism. - Overusing Abstract Factory when you only have one product: if you only have
Buttonwithout an accompanyingCheckbox, Abstract Factory is over-engineering. - Putting too much business logic in the factory: a factory should only concern itself with object creation, not embed order-processing logic, validation, etc.
- Forgetting to ensure consistency in Abstract Factory: if you let clients pick
Buttonfrom factory A andCheckboxfrom factory B, you’ve broken the pattern’s core purpose.
7. Conclusion
Factory Method is suitable when you need to delegate the creation of one object to a subclass, making code extensible following the Open/Closed Principle. Abstract Factory goes further: it ensures that a family of related objects is always created consistently, preventing incompatible components from being mixed together.
In practice with Spring Boot, you typically don’t manually write according to the GoF structure but instead leverage the DI container as a factory. But understanding the essence of these two patterns helps you read and understand library code (like DocumentBuilderFactory, ConnectionFactory in JDBC) and design extensible modules better.