Senior 15 min · March 05, 2026

Factory Pattern in Java Explained — Real-World Usage and Design

Master the Factory Pattern in Java with real-world examples, runnable code, and key design insights.

N
Naren Founder & Principal Engineer

20+ years shipping production Java in banking & fintech. Drawn from code that ran under real load.

Follow
Production
production tested
May 24, 2026
last updated
1,554
articles · all by Naren
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • Factory Pattern delegates object creation to a dedicated class, decoupling callers from concrete types
  • Key components: Product interface, concrete implementations, and a factory method (static or instance)
  • Performance: Factory overhead is negligible – same cost as direct instantiation
  • Production risk: Factory returning null or throwing unhandled exceptions causes cascading failures
  • Biggest mistake: Returning concrete types from factory instead of interface, breaking loose coupling
✦ Definition~90s read
What is Factory Pattern in Java?

The Factory pattern is a creational design pattern from the GoF catalog that decouples object creation from your business logic. Instead of sprinkling new calls throughout your code—which hard-codes concrete classes and makes testing or swapping implementations painful—you delegate instantiation to a dedicated method or class.

Imagine you walk into a coffee shop and say 'I'll have a latte.' You don't grind the beans, steam the milk, or assemble the cup yourself — the barista (the factory) handles all of that and hands you the finished drink.

In Java, this means you program to an interface or abstract class, and the factory decides which concrete implementation to return based on runtime context, configuration, or parameters. The core problem it solves is dependency rigidity: when you write new ArrayList<>() in ten places, changing to LinkedList requires ten edits; a factory centralizes that decision into one place.

In the Java ecosystem, the Factory pattern shows up everywhere: java.util.Calendar.getInstance(), java.text.NumberFormat.getNumberInstance(), and Spring’s BeanFactory are all factories. You’ll reach for it when you have a class hierarchy where the exact subclass isn’t known until runtime, or when construction logic is complex enough that you don’t want to repeat it.

The pattern has two main flavors: Factory Method (a single method that creates one type of object) and Abstract Factory (an interface for creating families of related objects without specifying their concrete classes). Don’t use a factory for simple objects with no variation—that’s over-engineering.

But when you’re building a payment system that needs to create CreditCardPayment, PayPalPayment, or CryptoPayment based on user choice, or a UI toolkit that must produce Windows-style vs. macOS-style buttons and menus, the Factory pattern is your go-to.

A common pitfall is confusing the Factory pattern with a simple static utility method. The real value comes from polymorphism: the factory itself is often an interface or abstract class, letting you swap entire creation strategies (e.g., a TestPaymentFactory that returns mocks).

In production Java codebases, you’ll see this combined with dependency injection frameworks like Spring or Guice, where the factory is injected and its create() method returns objects that are already wired with their dependencies. The pattern doesn’t eliminate new—it just moves it behind an abstraction layer that you can test, extend, and maintain without touching client code.

Plain-English First

Imagine you walk into a coffee shop and say 'I'll have a latte.' You don't grind the beans, steam the milk, or assemble the cup yourself — the barista (the factory) handles all of that and hands you the finished drink. In programming, a Factory does the same thing: you ask for an object by name, and the factory builds and returns it for you. You never need to know the messy details of how it was created.

You type new and you own that object for life. Want to swap implementations? You're rewriting constructors. Want to unit test? Hope that concrete class plays nice. The Factory Pattern in Java is your get-out-of-jail card: it hands creation logic to a dedicated method or object, so your code depends on abstractions, not hardcoded constructors. Without it, you get tight coupling, scattered instantiation, and a nightmare when requirements change.

What the Factory Pattern Actually Does in Java

The Factory pattern is a creational design pattern that delegates object instantiation to a separate method or class, decoupling the client from concrete implementations. Instead of calling new directly, you invoke a factory method that returns an instance of a common interface or abstract class. The core mechanic is simple: encapsulate the selection logic for which concrete class to instantiate, centralizing change when new types are added.

In practice, the factory method returns a type based on runtime parameters, configuration, or environment. This gives you a single point of control over object creation — essential when constructors are complex, require dependency injection, or when the concrete type must be resolved dynamically. The pattern is not about avoiding new; it's about managing where and how new happens.

Use a factory when you have multiple subclasses or implementations of an interface, and the correct one depends on input data or configuration. It shines in frameworks, plugin systems, and any codebase where adding a new implementation should not require modifying existing client code. The payoff is reduced coupling and a clear extension point — adding a new class means adding a new branch in the factory, not hunting down every switch or if-else in the codebase.

Factory vs. Abstract Factory
Factory method creates a single object; Abstract Factory creates families of related objects. Don't conflate them — the simpler Factory covers 80% of real needs.
Production Insight
A payment gateway integration used a switch statement across 15 services to instantiate processors. When a new gateway was added, three services missed the update, causing ClassNotFoundException at runtime.
The exact symptom: a 500 error with 'NoClassDefFoundError' for the new processor class, but only on two of five nodes because of a stale deployment.
Rule of thumb: if you find yourself writing the same switch/if-else for object creation in more than one place, extract it into a factory — or you will miss a branch in production.
Key Takeaway
Factory pattern decouples client code from concrete class selection, centralizing creation logic.
Use it when object type depends on runtime data or configuration — not as a default for every class.
A factory is a single point of change: adding a new implementation requires one new branch, not a hunt through the codebase.
Factory Pattern in Java — GoF & Real-World Usage THECODEFORGE.IO Factory Pattern in Java — GoF & Real-World Usage From scattered 'new' to clean, testable object creation Problem: Scattered 'new' Tight coupling, hard to test, violates DIP Factory Method Pattern Define interface, let subclasses decide instantiation Abstract Factory Pattern Create families of related objects without specifying classes Dependency Injection Integration Inject factory or product via DI container Testable Code Mock factories/products for isolated unit tests ⚠ Overusing factory pattern adds unnecessary complexity Use only when object creation logic varies or is complex THECODEFORGE.IO
thecodeforge.io
Factory Pattern in Java — GoF & Real-World Usage
Factory Pattern Java

The Problem With Scattering 'new' Everywhere

Let's make this concrete. Say you're building a payment processing system. At first you only support credit cards, so you write new CreditCardProcessor() in five different places. Six weeks later the product team adds PayPal. Now you're touching five files, and there's a real chance you miss one and introduce a subtle runtime bug.

This is called tight coupling — your calling code knows too much about the concrete type it's creating. It knows the class name, it knows what constructor arguments to pass, and it has to change every time the implementation changes.

The Factory Pattern breaks that coupling. Instead of your code saying 'I want a CreditCardProcessor', it says 'I want a PaymentProcessor for this payment type'. One central factory decides what gets built. When you add PayPal, you update one place: the factory. Everything else stays untouched.

This is the Open/Closed Principle in action — your system is open to extension (add new payment types) but closed to modification (don't touch existing calling code). That's the real value here, not just 'hiding the new keyword'.

TightCouplingProblem.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// This is what we're TRYING TO AVOID — tight coupling via direct instantiation.
// Imagine this pattern repeated across 10 service classes.

public class CheckoutService {

    public void processPayment(String paymentType, double amount) {

        // BAD: CheckoutService is tightly coupled to concrete classes.
        // Adding a new payment type means editing THIS method — and every
        // other class that does the same thing.
        if (paymentType.equals("CREDIT_CARD")) {
            CreditCardProcessor processor = new CreditCardProcessor(); // hard dependency
            processor.charge(amount);
        } else if (paymentType.equals("PAYPAL")) {
            PayPalProcessor processor = new PayPalProcessor();         // another hard dependency
            processor.charge(amount);
        }
        // Adding CRYPTO means editing this method. And the one in RefundService.
        // And the one in SubscriptionService. Painful.
    }
}

// The concrete classes we're leaking knowledge about:
class CreditCardProcessor {
    public void charge(double amount) {
        System.out.println("Charging $" + amount + " to credit card.");
    }
}

class PayPalProcessor {
    public void charge(double amount) {
        System.out.println("Charging $" + amount + " via PayPal.");
    }
}
Output
Charging $99.99 to credit card.
Watch Out:
If you find yourself copy-pasting an if/else block that creates objects in multiple service classes, that's your signal a Factory is overdue. Every duplicate block is a future bug waiting to happen.
Production Insight
Direct instantiation creates hidden dependencies across the codebase.
Adding a new product type requires editing N files — N-1 will be missed.
Rule: if you search for 'new' more than once, refactor to a factory.
Key Takeaway
Scattering 'new' couples code to concrete types.
Every duplicate instantiation is a future maintenance burden.
Factory centralises change — one edit, zero ripple.

Factory Pattern UML Structure — The Classic GoF Diagram

The Gang of Four (GoF) defines the Factory Method pattern with four key participants:

  • Product: An interface or abstract class that defines the type of objects the factory method creates.
  • ConcreteProduct: Classes that implement the Product interface, each providing a different behaviour.
  • Creator (also called Factory): An abstract class that declares the factory method (often abstract) and may contain core logic that uses products.
  • ConcreteCreator: Subclasses of Creator that override the factory method to return specific ConcreteProduct instances.

The following UML diagram illustrates these relationships. Notice how the Creator depends only on the Product interface, not on concrete classes. ConcreteCreators decide what to create by overriding factoryMethod().

Key Insight
The Creator is often an abstract class that contains the core logic and calls its own factory method. Subclasses override that method to change the created product. This is exactly how frameworks like Spring and Java's SDK work.
Production Insight
Understanding GoF structure helps when reading legacy code. Many frameworks use abstract Creator classes that you extend. Be prepared to implement factoryMethod() in subclasses. The Creator's someOperation() method typically calls factoryMethod() and works with the returned Product through polymorphism.
Key Takeaway
Factory Method pattern defines a separate method for object creation, letting subclasses decide the concrete class. The Creator remains decoupled from concrete products.
UML class diagram for Factory Method pattern
creates«interface»Product+operation()ConcreteProductA+operation()ConcreteProductB+operation()Creator#factoryMethod()+someOperation()ConcreteCreatorA#factoryMethod()ConcreteCreatorB#factoryMethod()

Applicability — When Should You Reach for the Factory Pattern?

The Factory Pattern (and its variants) fits specific situations. Here are the clearest criteria for when to use it:

  1. A class cannot anticipate the types of objects it must create. For example, a framework that manages user interface widgets doesn't know in advance what OS the application will run on — it relies on a factory to produce the appropriate set of buttons and dialogs.
  2. You want to delegate the responsibility of object creation to subclasses. When the core algorithm is fixed but the product creation varies, the Factory Method pattern lets subclasses decide the concrete class without modifying the algorithm.
  3. You need to isolate complex creation logic. If constructing an object involves configuration, dependency injection, or external resource fetching, centralising that logic in a factory prevents duplication and makes it testable.
  4. You anticipate adding new product types frequently. Factories make it easy to introduce new implementations by adding a single case without touching existing callers.
  5. You want to provide a library of related objects. The Abstract Factory pattern is ideal when a family of products must be used together (e.g., a consistent UI theme).

On the other hand, avoid factories when you have only one concrete product and no foreseeable variation. Premature abstraction adds complexity without payoff.

When Not to Use
If you only have one concrete product and no plans to extend, a factory adds unnecessary complexity. Use direct instantiation until variation arrives. A good rule: add a factory the third time you write the same creation code.
Production Insight
In enterprise apps, factories are introduced retrospectively during refactoring. The first sign is when a switch/if chain appears in multiple places. Extract that into a factory and watch your code become more maintainable. Another sign: unit tests that stub static methods — a factory interface eliminates the need for PowerMock.
Key Takeaway
Use Factory Pattern when the client code must be decoupled from concrete classes, or when creation logic may change or be extended later. Don't over-engineer before the need arises.

Building a Clean Factory Method Pattern From Scratch

The Factory Method Pattern introduces a dedicated creator — a single method (or class) whose only job is to decide which concrete object to build and return. The calling code only ever talks to the abstract type (an interface or abstract class), never to the concrete implementation.

Here's the structure: define an interface (the product), create concrete implementations of it, then write a factory class with a static method that takes a parameter and returns the right implementation. The magic is in the return type — it's always the interface, so the caller never sees the concrete class at all.

This works because of polymorphism. Your CheckoutService holds a reference of type PaymentProcessor. Whether that reference points to a CreditCardProcessor or a CryptoProcessor at runtime is none of its business. All it knows is 'this thing has a charge() method', and that's all it needs to know.

Let's build this properly — interface first, then implementations, then the factory, then the client.

PaymentProcessorFactory.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
// ─── Step 1: Define the Product Interface ───────────────────────────────────
// All payment processors must implement this contract.
// The factory will always return this type — callers never see the concrete class.
public interface PaymentProcessor {
    void charge(double amount);
    void refund(double amount);
}


// ─── Step 2: Concrete Implementations ───────────────────────────────────────

class CreditCardProcessor implements PaymentProcessor {
    @Override
    public void charge(double amount) {
        // Real impl would call a payment gateway SDK here
        System.out.println("[CreditCard] Charging $" + amount + " via Stripe gateway.");
    }

    @Override
    public void refund(double amount) {
        System.out.println("[CreditCard] Refunding $" + amount + " to card.");
    }
}

class PayPalProcessor implements PaymentProcessor {
    @Override
    public void charge(double amount) {
        System.out.println("[PayPal] Charging $" + amount + " via PayPal REST API.");
    }

    @Override
    public void refund(double amount) {
        System.out.println("[PayPal] Refunding $" + amount + " to PayPal account.");
    }
}

class CryptoProcessor implements PaymentProcessor {
    @Override
    public void charge(double amount) {
        System.out.println("[Crypto] Charging $" + amount + " worth of BTC on-chain.");
    }

    @Override
    public void refund(double amount) {
        // Crypto refunds are a manual process in reality
        System.out.println("[Crypto] Initiating manual refund of $" + amount + ".");
    }
}


// ─── Step 3: The Factory ─────────────────────────────────────────────────────
// This is the ONLY place that knows about concrete classes.
// To add a new payment type: add it here. Nothing else changes.

class PaymentProcessorFactory {

    // Using an enum as the key is safer than raw Strings — typos become
    // compile errors instead of silent runtime failures.
    public enum PaymentType {
        CREDIT_CARD, PAYPAL, CRYPTO
    }

    public static PaymentProcessor create(PaymentType type) {
        // Switch expression (Java 14+) — clean, exhaustive, no fall-through bugs
        return switch (type) {
            case CREDIT_CARD -> new CreditCardProcessor(); // factory decides the concrete type
            case PAYPAL      -> new PayPalProcessor();
            case CRYPTO      -> new CryptoProcessor();
            // No default needed — enum exhaustiveness is checked at compile time
        };
    }
}


// ─── Step 4: The Client (CheckoutService) ───────────────────────────────────
// Notice: CheckoutService imports ZERO concrete processor classes.
// It only knows about PaymentProcessor (the interface) and PaymentProcessorFactory.

class CheckoutService {
    private final PaymentProcessorFactory.PaymentType preferredPaymentType;

    public CheckoutService(PaymentProcessorFactory.PaymentType preferredPaymentType) {
        this.preferredPaymentType = preferredPaymentType;
    }

    public void completePurchase(double orderTotal) {
        // Ask the factory for the right processor — we don't care which class comes back
        PaymentProcessor processor = PaymentProcessorFactory.create(preferredPaymentType);

        processor.charge(orderTotal);  // polymorphic call — works for any implementation
        System.out.println("Purchase complete. Total charged: $" + orderTotal);
    }
}


// ─── Step 5: Main — wire it together and run ─────────────────────────────────
class Main {
    public static void main(String[] args) {
        // Simulate three different customers with different payment preferences
        CheckoutService cardCustomer   = new CheckoutService(PaymentProcessorFactory.PaymentType.CREDIT_CARD);
        CheckoutService paypalCustomer = new CheckoutService(PaymentProcessorFactory.PaymentType.PAYPAL);
        CheckoutService cryptoCustomer = new CheckoutService(PaymentProcessorFactory.PaymentType.CRYPTO);

        cardCustomer.completePurchase(49.99);
        paypalCustomer.completePurchase(149.00);
        cryptoCustomer.completePurchase(999.00);
    }
}
Output
[CreditCard] Charging $49.99 via Stripe gateway.
Purchase complete. Total charged: $49.99
[PayPal] Charging $149.0 via PayPal REST API.
Purchase complete. Total charged: $149.0
[Crypto] Charging $999.0 worth of BTC on-chain.
Purchase complete. Total charged: $999.0
Pro Tip:
Always use an enum (or a registry map) as the factory's key instead of a raw String. String-based factories fail silently at runtime with a null or exception on a typo. Enum-based factories make invalid types a compile error — caught before the code ever runs.
Production Insight
Enum-based factories shift errors from runtime to compile time.
But adding a new enum value without updating the switch still breaks production.
Rule: always include a default case or a static test that verifies all enum values are mapped.
Key Takeaway
Use enum keys, not strings, in factory switches.
Compile-time safety beats runtime null checks.
Test that every enum value has a corresponding case.

The simple Factory Method is perfect for creating one type of object. But sometimes you need to create a family of related objects that must be used together. That's where the Abstract Factory Pattern comes in — think of it as a factory of factories.

A great real-world example: a UI toolkit. A Windows-themed UI needs a Windows-style Button AND a Windows-style Dialog AND a Windows-style TextField — all matching. A Mac UI needs the Mac versions of all three. You can't mix a Mac Button with a Windows Dialog; they need to be consistent.

The Abstract Factory defines an interface for creating each type of object in the family. Concrete factories (WindowsUIFactory, MacUIFactory) implement that interface and produce the matching set. The application only ever holds a reference to the abstract factory — swap the factory, and every single component produced is automatically the right theme.

This is a step up in complexity from the simple factory, so only reach for it when you genuinely have a consistency requirement across multiple related types.

AbstractUIFactory.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
// ─── Product Interfaces ──────────────────────────────────────────────────────
// Each UI component type gets its own interface.

interface Button {
    void render();
    void onClick();
}

interface Dialog {
    void show(String message);
}


// ─── Windows Concrete Products ───────────────────────────────────────────────

class WindowsButton implements Button {
    @Override
    public void render() {
        System.out.println("[Windows] Rendering a flat, square button with ClearType font.");
    }
    @Override
    public void onClick() {
        System.out.println("[Windows] Playing Windows click sound effect.");
    }
}

class WindowsDialog implements Dialog {
    @Override
    public void show(String message) {
        System.out.println("[Windows] Modal dialog box: " + message);
    }
}


// ─── Mac Concrete Products ───────────────────────────────────────────────────

class MacButton implements Button {
    @Override
    public void render() {
        System.out.println("[Mac] Rendering a rounded, glossy button with SF Pro font.");
    }
    @Override
    public void onClick() {
        System.out.println("[Mac] Playing macOS click haptic feedback.");
    }
}

class MacDialog implements Dialog {
    @Override
    public void show(String message) {
        System.out.println("[Mac] HUD-style dialog sheet: " + message);
    }
}


// ─── Abstract Factory Interface ──────────────────────────────────────────────
// This is the core of the Abstract Factory pattern.
// It declares a creation method for EACH product in the family.

interface UIComponentFactory {
    Button createButton();
    Dialog createDialog();
    // If we add TextField later, we add createTextField() here
    // and implement it in ALL concrete factories — compiler enforces completeness.
}


// ─── Concrete Factories ──────────────────────────────────────────────────────
// Each factory produces a CONSISTENT family of components.
// You can't accidentally mix Mac buttons with Windows dialogs.

class WindowsUIFactory implements UIComponentFactory {
    @Override
    public Button createButton() {
        return new WindowsButton(); // guaranteed Windows-themed
    }
    @Override
    public Dialog createDialog() {
        return new WindowsDialog(); // guaranteed Windows-themed
    }
}

class MacUIFactory implements UIComponentFactory {
    @Override
    public Button createButton() {
        return new MacButton(); // guaranteed Mac-themed
    }
    @Override
    public Dialog createDialog() {
        return new MacDialog(); // guaranteed Mac-themed
    }
}


// ─── Application — only depends on the abstract factory interface ─────────────

class Application {
    private final Button confirmButton;
    private final Dialog errorDialog;

    // The factory is injected — Application has ZERO knowledge of Windows vs Mac.
    // Swap the factory, and every component produced automatically changes theme.
    public Application(UIComponentFactory factory) {
        this.confirmButton = factory.createButton();
        this.errorDialog   = factory.createDialog();
    }

    public void run() {
        confirmButton.render();
        confirmButton.onClick();
        errorDialog.show("File not found — please check the path and try again.");
    }
}


// ─── Main ────────────────────────────────────────────────────────────────────

class UIMain {
    public static void main(String[] args) {
        String operatingSystem = System.getProperty("os.name").toLowerCase();

        // Decide which factory to use ONCE — at the entry point of the app.
        // Everything downstream gets consistent components automatically.
        UIComponentFactory factory;
        if (operatingSystem.contains("mac")) {
            factory = new MacUIFactory();
        } else {
            factory = new WindowsUIFactory();
        }

        Application app = new Application(factory);
        app.run();
    }
}
Output
// On Windows:
[Windows] Rendering a flat, square button with ClearType font.
[Windows] Playing Windows click sound effect.
[Windows] Modal dialog box: File not found — please check the path and try again.
// On Mac:
[Mac] Rendering a rounded, glossy button with SF Pro font.
[Mac] Playing macOS click haptic feedback.
[Mac] HUD-style dialog sheet: File not found — please check the path and try again.
Interview Gold:
Interviewers love asking 'when would you use Abstract Factory over Factory Method?' The answer: use Factory Method when you create ONE type of object. Use Abstract Factory when you need FAMILIES of related objects that must stay consistent with each other. Nail this distinction and you'll stand out.
Production Insight
Adding a new product family requires creating a whole new concrete factory — that's N new classes.
The complexity is justified only when cross-product consistency is mandatory.
Rule: if you only need one product type, start with Factory Method; Abstract Factory is for multi-product consistency.
Key Takeaway
Abstract Factory ensures families of objects stay consistent.
But it increases class count — don't over-engineer.
Start with Factory Method; scale to Abstract Factory only when needed.

Factory Pattern With Dependency Injection

In real production systems, factories rarely work in isolation — they're often integrated with dependency injection (DI) containers like Spring or Guice. The DI container itself is a factory: it configures bean definitions and creates objects when requested. But sometimes you still need the flexibility of a custom factory inside a container-managed application.

For example, Spring's ApplicationContext acts as an abstract factory: you ask for a bean by type or name, and it returns a fully wired instance. However, when you need to decide at runtime which implementation to create (e.g., payment processor based on user selection), you can't wire every possible processor as a bean upfront — you need a custom factory that uses the container to fetch the right implementation.

A clean approach: inject a Map<String, PaymentProcessor> where the map keys are payment types and the values are prototype-scoped beans. Spring creates the map at startup, and your factory simply looks up the right processor. This avoids switch/if altogether and gives you compile-time safety without manual factory maintenance.

PaymentProcessorFactoryWithDI.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// ─── Spring-based factory using dependency injection ────────────────────────

interface PaymentProcessor {
    void charge(double amount);
}

// Concrete implementations are Spring beans (prototype scope for thread safety)
@Component @Scope("prototype")
class CreditCardProcessor implements PaymentProcessor { ... }

@Component @Scope("prototype")
class PayPalProcessor implements PaymentProcessor { ... }


// ─── Factory that uses injected map ──────────────────────────────────────────

@Component
class PaymentProcessorFactory {

    // Spring injects a Map<String, PaymentProcessor> where keys are bean names
    private final Map<String, PaymentProcessor> processorByType;

    @Autowired
    public PaymentProcessorFactory(Map<String, PaymentProcessor> processorByType) {
        this.processorByType = processorByType;
    }

    public PaymentProcessor create(String type) {
        PaymentProcessor processor = processorByType.get(type);
        if (processor == null) {
            throw new IllegalArgumentException("Unknown payment type: " + type);
        }
        return processor;
    }
}

// Client code is clean:
@Service
class CheckoutService {
    @Autowired
    private PaymentProcessorFactory factory;

    public void checkout(String type, double amount) {
        PaymentProcessor processor = factory.create(type);
        processor.charge(amount);
    }
}

// Adding a new payment type? Just add a new @Component (and a bean name in properties).
// The factory and client code don't change. Open/Closed Principle in practice.
Pro Tip:
When integrating factory with DI, prefer injection of a Map of beans over writing manual switch statements. Spring's Map injection ensures you never forget to register a new implementation — if the bean exists, it's automatically included.
Production Insight
Using DI with a factory reduces boilerplate but introduces a runtime dependency on the container.
Misconfigured bean scopes (e.g., singleton in multi-threaded factory) cause data races.
Rule: for stateful processors, use prototype scope; for stateless, use singleton; never assume default scope is safe.
Key Takeaway
DI containers can act as backbone for factory pattern.
Injecting a Map of beans eliminates switch statements entirely.
But watch bean scopes: prototype for stateful, singleton for stateless.

Testing With the Factory Pattern

One of the biggest advantages of the Factory Pattern is how it improves testability. When your calling code depends on an interface and creates objects through a factory, you can easily swap real implementations with mocks or stubs during unit tests. Without a factory, you'd have to modify the class under test or resort to bytecode manipulation (PowerMock) — both are fragile and slow.

Consider the CheckoutService from earlier: it depends on PaymentProcessorFactory.PaymentType to get a processor. In a unit test, instead of creating a real CreditCardProcessor (which might call an external gateway), you can inject a mock PaymentProcessorFactory that returns a mock processor. Or better, refactor the factory to be an interface and inject a test double.

A cleaner approach: make the factory a dependency of the service (dependency injection) rather than calling a static factory method. Then in tests, you provide a test factory that returns canned responses. This follows the Dependency Inversion Principle and makes your code truly testable.

The pattern itself doesn't guarantee testability — it's how you wire it. Static factories are less testable than instance factories that can be swapped. Always prefer an instance factory with an interface that can be mocked.

FactoryPatternTestability.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// ─── Interface-based factory (testable) ─────────────────────────────────────

interface PaymentProcessorFactory {
    PaymentProcessor create(PaymentType type);
}

// Real implementation
class RealPaymentProcessorFactory implements PaymentProcessorFactory {
    @Override
    public PaymentProcessor create(PaymentType type) {
        return switch (type) {
            case CREDIT_CARD -> new CreditCardProcessor();
            case PAYPAL -> new PayPalProcessor();
        };
    }
}

// Client that depends on factory interface
class CheckoutService {
    private final PaymentProcessorFactory factory;

    // Factory is injected — easy to mock in tests
    public CheckoutService(PaymentProcessorFactory factory) {
        this.factory = factory;
    }

    public void checkout(double amount, PaymentType type) {
        PaymentProcessor processor = factory.create(type);
        processor.charge(amount);
    }
}


// ─── Test with Mockito ───────────────────────────────────────────────────────
class CheckoutServiceTest {

    @Test
    void shouldChargeViaCreditCard() {
        // Given a mock factory
        PaymentProcessor mockProcessor = mock(PaymentProcessor.class);
        PaymentProcessorFactory mockFactory = mock(PaymentProcessorFactory.class);
        when(mockFactory.create(PaymentType.CREDIT_CARD)).thenReturn(mockProcessor);

        CheckoutService service = new CheckoutService(mockFactory);

        // When
        service.checkout(100.0, PaymentType.CREDIT_CARD);

        // Then
        verify(mockProcessor).charge(100.0);
    }
}

// Notice: No real CreditCardProcessor is instantiated. No external dependencies.
// Test runs in milliseconds and never touches a database or network.
// That's the power of factory + DI.
Key Insight:
A static factory method (like PaymentProcessorFactory.create()) cannot be mocked easily. If you need testability, make the factory an interface and inject it. Your unit tests will thank you.
Production Insight
Legacy code with static factories and 'new' calls is notoriously hard to test.
Refactoring to interface-based factory is the first step to covering untested code with unit tests.
Rule: if you can't instantiate a class without touching a database, it's a factory candidate.
Key Takeaway
Factory pattern + DI makes unit testing trivial.
Static factories kill testability — prefer instance factories.
Testability is the real ROI of the Factory Pattern.

Factory Pattern in the Java Standard Library — It's Already Everywhere

One of the best ways to solidify a pattern is to see where it already exists in code you use every day. The Java standard library is full of Factory Method implementations — and spotting them will train your eye to recognise the pattern instinctively.

Calendar.getInstance() returns the right Calendar subclass for your locale — you never call new GregorianCalendar() directly. NumberFormat.getCurrencyInstance() returns a locale-appropriate formatter. DriverManager.getConnection() returns the right JDBC Connection implementation for whichever database driver you've registered on the classpath.

Spring Framework takes this further with its ApplicationContext, which is essentially a giant Abstract Factory — you ask for a bean by type or name and Spring decides what concrete object to build and return, handling lifecycle, proxies, and dependency injection transparently.

Studying how these APIs are designed teaches you the pattern better than any textbook. Next time you call a static getInstance() or create() method in Java, ask yourself: 'What is this hiding from me, and why?'

FactoryPatternInJavaSDK.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import java.text.NumberFormat;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;
import java.util.Set;

public class FactoryPatternInJavaSDK {

    public static void main(String[] args) {

        // ── Calendar.getInstance() ───────────────────────────────────────────
        // Returns a BuddhistCalendar in Thailand, a JapaneseImperialCalendar in Japan,
        // a GregorianCalendar everywhere else. You don't pick — the factory picks.
        Calendar today = Calendar.getInstance(); // factory method — returns correct subclass
        System.out.println("Today's year: " + today.get(Calendar.YEAR));
        System.out.println("Calendar impl class: " + today.getClass().getSimpleName());

        // ── NumberFormat factory methods ─────────────────────────────────────
        // Same interface, radically different formatting behaviour by locale.
        // You never call `new SomeCurrencyFormat()` — the factory handles it.
        NumberFormat usDollarFormat   = NumberFormat.getCurrencyInstance(Locale.US);
        NumberFormat euroFormat       = NumberFormat.getCurrencyInstance(Locale.GERMANY);
        NumberFormat japanYenFormat   = NumberFormat.getCurrencyInstance(Locale.JAPAN);

        double price = 1299.99;
        System.out.println("US:      " + usDollarFormat.format(price));  // factory-produced formatter
        System.out.println("Germany: " + euroFormat.format(price));      // different impl, same interface
        System.out.println("Japan:   " + japanYenFormat.format(price));  // yet another impl

        // ── Collections factory methods (Java 9+) ────────────────────────────
        // List.of() and Set.of() return private internal implementations
        // (ImmutableCollections$List12, etc.) — you never see or care about the
        // concrete class. Classic factory pattern: ask for a List, get an optimised impl.
        List<String> paymentMethods = List.of("CREDIT_CARD", "PAYPAL", "CRYPTO");
        Set<String>  currencies     = Set.of("USD", "EUR", "JPY", "BTC");

        System.out.println("Payment methods: " + paymentMethods);
        System.out.println("List impl class: " + paymentMethods.getClass().getName()); // not java.util.ArrayList!
        System.out.println("Currencies: " + currencies);
    }
}
Output
Today's year: 2025
Calendar impl class: GregorianCalendar
US: $1,299.99
Germany: 1.299,99 €
Japan: ¥1,300
Payment methods: [CREDIT_CARD, PAYPAL, CRYPTO]
List impl class: java.util.ImmutableCollections$List12
Currencies: [USD, BTC, EUR, JPY]
Pro Tip:
Run paymentMethods.getClass().getName() on a List.of() result and look at what comes back. It's not ArrayList — it's an internal optimised class. This is the Factory Pattern protecting you from implementation details that the JDK team can freely change in future versions without breaking your code.
Production Insight
Real-world factories in the JDK are designed for extreme performance — ImmutableCollections uses compact internal representations.
If you ever need to mimic that, consider using factory methods from the JDK as benchmarks.
Rule: study the JDK's factories; they represent decades of production-hardened design choices.
Key Takeaway
Java's standard library is a living factory pattern reference.
Calendar, NumberFormat, Collections — each is a factory.
Recognising these patterns in familiar APIs solidifies the concept.

Simple Factory vs Factory Method vs Abstract Factory — A Side-by-Side Comparison

While all three patterns centralise object creation, they differ in flexibility, complexity, and coupling. The following table highlights key differences:

AspectSimple FactoryFactory MethodAbstract Factory
PurposeCentralise creation of a single product typeDelegate creation to subclasses, create one product typeCreate families of related products
StructureSingle static/instance method with a switchAbstract Creator with factoryMethod() overridden by subclassesInterface with multiple creation methods, each returning a product type
FlexibilityLow — hard-coded mapping in one methodMedium — subclasses can decide productHigh — can swap entire families
CouplingClient depends on factory, product interfaceClient depends on Creator abstract classClient depends on AbstractFactory interface
ExtensibilityAdd case in factory methodAdd new ConcreteCreator subclassAdd new ConcreteFactory (must implement all methods)
ComplexityLowMediumHigh
When to useSimple object creation varying by one parameterFramework where subclasses define productsWhen multiple products must be consistent (e.g., UI themes)

A Simple Factory is not a true GoF pattern but a common idiom — often sufficient for small projects. Factory Method is the formal GoF pattern, allowing inheritance-based customisation. Abstract Factory is the most powerful but also the most complex. Choose based on how many product types you need and whether they must be consistent.

Interview Favourite
Expect questions on the difference between Simple Factory, Factory Method, and Abstract Factory. Know when each is appropriate and be ready to give real-world examples.
Production Insight
In production, Simple Factory is often used internally within a module. Factory Method appears in frameworks (e.g., servlet containers). Abstract Factory is common in cross-platform libraries. Choose the variant that matches your need for flexibility vs. simplicity.
Key Takeaway
Simple Factory for one product, Factory Method for subclass control, Abstract Factory for product families. Complexity should match growth: start simple, evolve as needed.

Factory Pattern — Pros and Cons

No pattern is a silver bullet. Here are the trade-offs to consider when adopting the Factory Pattern:

ProsCons
Decoupling — Client code depends on interfaces, not concrete classesIncreased complexity — Adds extra classes and indirection
Open/Closed Principle — New products can be added without modifying existing callersMay introduce unnecessary abstraction — Overuse for single-product scenarios
Testability — Easy to mock factories and swap implementations in testsMore classes to maintain — Each new product needs a new class and factory update
Centralised creation logic — Changes to object construction happen in one placeCan become a God object — If the factory handles too many types, it grows unwieldy
Reusable — Same factory can be used across multiple clientsNot suitable for very simple creation — When constructor logic is trivial, factory adds overhead
Supports Dependency Inversion — High-level modules don't depend on low-level detailsStatic factories break testability — If not designed as interface-injected

The key is to evaluate whether the benefits outweigh the costs in your specific context. For a payment system with frequent gateway additions, the factory pays for itself quickly. For a utility class that creates one internal object, it's over-engineering.

Overuse Alert
Factories can become an anti-pattern if you create them prematurely. Only add a factory when you have at least two variations or anticipate future ones. A good heuristic: add a factory the third time you write the same new call.
Production Insight
In production, factory overhead is negligible. The real cost is cognitive: a new developer must understand where objects come from. Keep factories focused and well-named. If a factory has more than 10 product types, consider splitting into multiple factories.
Key Takeaway
Factory Pattern trades a small increase in initial complexity for large gains in maintainability and testability when variation exists. Avoid premature abstraction.

Practice Exercises — Sharpen Your Factory Pattern Skills

The best way to internalise the Factory Pattern is to implement it yourself. Try these exercises, each reflecting a real-world scenario:

1. Payment Gateway Factory You're building an e-commerce platform that supports Stripe, PayPal, and Square. Define a PaymentGateway interface with processPayment(double amount) and refundPayment(double amount). Create concrete classes for each gateway. Build a factory that takes a PaymentGatewayType enum and returns the correct gateway. Hint: Use a switch expression (Java 14+) or a Map<String, PaymentGateway>.

2. Notification Factory A mobile app must send notifications via Email, SMS, and Push. Create a NotificationSender interface with send(String recipient, String message). Implement concrete senders for each channel. The factory should accept a NotificationChannel enum. Add a UserNotificationPreferences that the factory could use to route notifications. Hint: Think about thread safety if senders are stateful.

3. Database Driver Factory You need to connect to MySQL, PostgreSQL, or MongoDB based on a configuration string. Define a DatabaseConnection interface with connect() and executeQuery(String sql). Implement drivers for each database. The factory reads the connection string and returns the appropriate driver. Hint: Use String.contains() or parse the JDBC prefix (e.g., "jdbc:mysql"). Extend the factory to return a connection pool instead of a single connection.

4. Logging Framework Factory Your application needs Console, File, and Cloud logging. Define a Logger interface with log(LogLevel level, String message). Create concrete loggers. The factory should support dynamic switching (e.g., from config file). Challenge: Make the factory return a composite logger that sends to multiple destinations.

5. Shape Factory (for a Drawing App) You're building a vector drawing tool that supports Circle, Rectangle, and Triangle. Each shape must implement calculateArea(), draw(), and resize(double factor). The factory creates shapes based on user input. Hint: Use an enum for shape types and provide additional parameters (like radius for Circle) via a builder pattern inside the factory.

Try to implement each exercise with JUnit tests that mock the factory. This will solidify both the pattern and testability.

Try Implementing
Implement at least one exercise fully with unit tests to see the pattern in action. Start with the Payment Gateway Factory — it's the closest to real-world production code.
Production Insight
These exercises mirror real-world use cases. Payment gateways, notifications, databases, logging, and drawing shapes are common scenarios where factories shine. Practicing with them will make you faster at recognising factory opportunities in your daily work.
Key Takeaway
Practice implementing factories for diverse scenarios; it will become second nature in design discussions and interviews. Each exercise reinforces decoupling and testability.

What the Factory Method Design Pattern Actually Is

Most blog posts will tell you the Factory Method is about 'defining an interface for creating an object, but letting subclasses decide which class to instantiate.' That's the textbook definition. Here's what it means in practice: you've got a method that returns a product, and you've hidden the concrete type behind an abstraction. The caller doesn't know — and shouldn't care — whether it gets a DieselEngine or a HydrogenFuelCell. That's the entire point.

Why does this matter? Because every time you write new GasolineEngine() scattered across your codebase, you've introduced a compile-time dependency on a concrete class. Change that to new ElectricMotor() and you're hunting down every instantiation. The Factory Method centralizes that decision. One method, one place to change. The subclasses decide which concrete object to create, and the client just calls the factory method and gets back something it can use through the interface.

This isn't abstract theory. This is the pattern that powers Spring's FactoryBean, JPA's EntityManagerFactory, and half the instantiation logic in the JDK. If you're not using it, you're doing manual work the framework could be doing for you.

EngineFactory.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// io.thecodeforge — java tutorial

interface Engine {
    void start();
}

class DieselEngine implements Engine {
    @Override public void start() {
        System.out.println("Diesel engine rumbles to life");
    }
}

class ElectricMotor implements Engine {
    @Override public void start() {
        System.out.println("Electric motor whirs silently");
    }
}

// Factory Method — the caller never touches 'new'
abstract class Vehicle {
    protected abstract Engine createEngine();
    
    public void start() {
        Engine engine = createEngine();
        engine.start();
    }
}

class Truck extends Vehicle {
    @Override protected Engine createEngine() {
        return new DieselEngine();
    }
}

class EVCar extends Vehicle {
    @Override protected Engine createEngine() {
        return new ElectricMotor();
    }
}
Output
// Calling: new Truck().start()
// Output: Diesel engine rumbles to life
// Calling: new EVCar().start()
// Output: Electric motor whirs silently
Production Trap:
Never put conditional logic (if/switch) inside the factory method to decide which concrete class to return. That defeats the purpose — you've just moved the hardcoding. The whole point is that each subclass makes that decision. If you need runtime configuration, use Abstract Factory or pass a config parameter.
Key Takeaway
A factory method hides the concrete type behind an abstraction. The caller doesn't know what it gets, and it shouldn't care.

Key Components — The Moving Parts You Actually Need to Know

Every Factory Method implementation has four players. Miss one, and the pattern breaks down into a tangled mess of dependencies. Here they are, no UML diagram fluff:

Product — The interface or abstract class defining what the factory method returns. This is your contract. Keep it narrow. A product interface with fifteen methods is a sign you're doing it wrong.

ConcreteProduct — The actual implementation. DieselEngine, ElectricMotor, whatever. These are the classes your factory method instantiates. They should never leak outside the factory chain.

Creator — The abstract class or interface declaring the factory method. This is where createEngine() lives. The Creator can also provide a default implementation that returns a common product, letting subclasses override when needed.

ConcreteCreator — The subclass that overrides the factory method to return a specific ConcreteProduct. This is the only place where new appears for that product type.

That's it. Four roles. The Creator doesn't know what ConcreteProduct it'll get — it just knows it'll get something that conforms to the Product interface. This is the Hollywood Principle in action: don't call us, we'll call you.

FactoryComponents.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// io.thecodeforge — java tutorial

// Product
interface PaymentGateway {
    boolean charge(double amount);
}

// ConcreteProduct
class StripeGateway implements PaymentGateway {
    @Override public boolean charge(double amount) {
        System.out.println("Charging $" + amount + " via Stripe");
        return true;
    }
}

class PayPalGateway implements PaymentGateway {
    @Override public boolean charge(double amount) {
        System.out.println("Charging $" + amount + " via PayPal");
        return true;
    }
}

// Creator
abstract class PaymentService {
    protected abstract PaymentGateway createGateway();
    
    public boolean processPayment(double amount) {
        PaymentGateway gateway = createGateway();
        return gateway.charge(amount);
    }
}

// ConcreteCreator
class StripePaymentService extends PaymentService {
    @Override protected PaymentGateway createGateway() {
        return new StripeGateway();
    }
}

class PayPalPaymentService extends PaymentService {
    @Override protected PaymentGateway createGateway() {
        return new PayPalGateway();
    }
}
Output
// new StripePaymentService().processPayment(49.99)
// Output: Charging $49.99 via Stripe
// new PayPalPaymentService().processPayment(29.99)
// Output: Charging $29.99 via PayPal
Senior Shortcut:
If you find yourself writing a Creator class that never has a default factory method implementation, ask if you really need the pattern. Sometimes a static factory method on the Product interface is cleaner. Factory Method shines when the Creator has shared logic that uses the product.
Key Takeaway
Four roles — Product, ConcreteProduct, Creator, ConcreteCreator. The Creator only knows the interface. The ConcreteCreator knows the concrete class.

Factory Pattern vs. Direct Construction — The Performance and Memory Argument

Every 'new' call in Java locks you into a concrete class at compile time. The Factory Pattern defers object creation to runtime, which changes how the JVM handles memory and dispatch. Direct construction with 'new' forces early binding — the JVM knows the exact type at compile time and can inline constructors. A Factory Method, by contrast, uses virtual dispatch through an interface or abstract class. This adds a single vtable lookup per object creation (negligible for most apps). The real cost is in object lifecycles: factories let you pool objects, cache frequently used instances, or return singletons without the caller knowing. In high-throughput systems, a factory can reduce GC pressure by reusing objects instead of allocating fresh ones. The tradeoff is that direct 'new' is trivially faster for one-off objects with no reuse strategy. Always measure before optimizing. But if you need to decouple creation logic from business logic, the factory's flexibility outweighs the nanosecond overhead of a virtual call.

FactoryVsNew.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// io.thecodeforge — java tutorial

// Direct construction — locked to ArrayList
List<String> list1 = new ArrayList<>();

// Factory method — deferred to implementation
List<String> list2 = ListFactory.newList();

class ListFactory {
    static List<String> newList() {
        return new ArrayList<>(); // swap to LinkedList anytime
    }
}

// Without factory: every call site must change
// With factory: one line change in ListFactory
Production Trap:
Never pre-optimize by avoiding factories. The real performance killer is object allocation churn, not a virtual method call. Profile first.
Key Takeaway
Factory patterns trade compile-time binding for runtime flexibility, enabling object pooling and reduced GC pressure at the cost of one virtual dispatch per creation.

Why the Factory Pattern Exists — It's a Contract, Not a Crutch

Many developers think Factory Pattern is just a fancy way to call 'new' from a different class. That misses the point. The Factory Pattern exists to enforce a contract between the creator and the product. When you call a factory method, you're saying: 'I need an object that satisfies this interface, but I don't care about its concrete type or how it's built.' This decouples the caller from the construction logic, enabling dependency inversion. Without a factory, every client class must import both the interface and all concrete implementations. With a factory, the client depends only on the abstraction. This is not about code organization — it's about preventing your codebase from turning into a tangled mess of compile-time dependencies. In large systems, factories are the difference between swapping an implementation by changing one line vs. tracking down hundreds of scattered 'new' calls. The factory pattern is a contract enforcer: it says 'the creator knows how to build it; the client only knows how to use it.'

ContractFactory.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// io.thecodeforge — java tutorial

interface Database {
    void connect();
}

class MySQLDb implements Database {
    public void connect() { /* MySQL */ }
}

class PostgresDb implements Database {
    public void connect() { /* Postgres */ }
}

// Factory enforces contract — client never sees MySQLDb
class DatabaseFactory {
    static Database create(String type) {
        return switch (type) {
            case "mysql" -> new MySQLDb();
            case "postgres" -> new PostgresDb();
            default -> throw new IllegalArgumentException();
        };
    }
}
Production Trap:
Don't use a factory for every object. Only apply it where the concrete type varies at runtime or where you need to control object lifecycle.
Key Takeaway
A factory is a contractual boundary: it enforces that clients depend on abstractions, not concrete classes, preventing compile-time coupling across your codebase.
● Production incidentPOST-MORTEMseverity: high

Payment Factory Returns Null After Config Migration

Symptom
All payment transactions fail with NullPointerException at PaymentProcessorFactory.create(). The stack trace points to a switch expression returning null.
Assumption
The team assumed the factory would always handle all enum values because the old switch had a default case that mapped to a fallback processor.
Root cause
During a configuration migration, a new enum value 'CRYPTO' was added to PaymentType but the switch expression in the factory was not updated. The switch (Java 14+) had no default case because the team relied on compile-time exhaustiveness, but the new value was added after compilation. The factory returned null, and the caller didn't check for null.
Fix
Add a default case that throws a descriptive IllegalArgumentException or returns a fallback implementation. Also add null checks at the caller level and central logging in the factory to capture missing type mappings.
Key lesson
  • Never assume factory exhaustiveness survives deployment — new enum values can be added without recompiling the factory.
  • Always include a default case in factory switch expressions, even with exhaustive enums, to guard against future additions.
  • Callers must always handle null return values from factories, either via Optional or explicit null checks.
Production debug guideSymptom → Action table for common factory failures in production4 entries
Symptom · 01
Factory returns null for a valid product type
Fix
Check switch/if exhaustiveness. Add logging at factory entry and exit. Verify enum values match constants.
Symptom · 02
Factory returns wrong implementation class
Fix
Inspect factory logic for incorrect condition. Check for copy-paste errors in case branches. Validate that the key parameter is not being transformed incorrectly.
Symptom · 03
Factory throws ClassNotFoundException or NoClassDefFoundError
Fix
Ensure all implementation classes are on the classpath. For reflective factories, verify class.forName() arguments. Use compile-time registration (enum+switch) instead of reflection.
Symptom · 04
Factory instantiates objects with missing dependencies
Fix
If factory uses constructor injection, verify that dependencies are available. Consider using a dependency injection container or a Builder inside the factory.
★ Factory Cheat SheetQuick commands and checks when factory behaviour breaks
Null from factory
Immediate action
Add null check at caller and log the factory input parameter
Commands
System.err.println("Factory input: " + type);
Add default case in switch: default -> throw new IllegalArgumentException("Unknown type: " + type);
Fix now
Wrap factory call in Optional.ofNullable() and handle absent case
Wrong object returned+
Immediate action
Log the actual class name of the returned object
Commands
System.out.println("Created: " + result.getClass().getName());
Verify the mapping logic in the factory method
Fix now
Add a guard trace: if (!expectedClass.isInstance(result)) { ... }
Factory throwing exception on new type+
Immediate action
Check if the type enum value was added but factory not updated
Commands
grep -r 'PAYMENT_TYPE' src/main/java/**/factory/
Verify that the enum and factory switch are in the same module
Fix now
Add the missing case to the switch and recompile
Factory Method vs Abstract Factory
AspectFactory MethodAbstract Factory
PurposeCreate one type of productCreate families of related products
StructureSingle factory class with one create() methodFactory interface with one method per product type
When to useObject creation logic varies by a single parameterMultiple related objects must be consistent with each other
Adding new product typesAdd a new case to the factory switch/ifAdd a new concrete factory class implementing all methods
ComplexityLow — easy to start withHigher — more classes, but more flexible
Real-world Java exampleCalendar.getInstance()Spring ApplicationContext (bean factory)
Coupling to concrete classesIsolated to factory class onlyIsolated to concrete factory classes only
Client code changes when adding productsNone — just update the factoryNone — just add a new concrete factory

Key takeaways

1
The Factory Pattern's real value is not 'hiding new'
it's enforcing the Open/Closed Principle so you can add new types by updating one place (the factory) without touching any calling code.
2
Always return the interface type from a factory method, never the concrete class. The moment calling code can see the concrete type, the encapsulation is broken and the pattern stops paying dividends.
3
Use Factory Method for one product type, Abstract Factory for a family of related products that must stay consistent
like a full themed UI kit where mixing Windows buttons with Mac dialogs would break the design.
4
Java's standard library is full of production-grade factory pattern examples (Calendar.getInstance, NumberFormat.getCurrencyInstance, List.of)
study these APIs to internalise what good factory design looks like in the wild.
5
Prefer instance factories (with interfaces) over static factories for testability
static factories cannot be mocked in unit tests.

Common mistakes to avoid

3 patterns
×

Using raw String keys in the factory switch statement

Symptom
A typo like 'CREDITCARD' instead of 'CREDIT_CARD' returns null or throws an unhandled exception at runtime, often in production
Fix
Use an enum as the factory key. Typos in enum values are compile-time errors, not runtime surprises. Your IDE will also autocomplete enum values, eliminating the problem entirely.
×

Returning a concrete class type from the factory method instead of the interface

Symptom
Calling code starts using concrete-class-specific methods (e.g., CreditCardProcessor.setCardNumber()), tightly coupling it to one implementation and defeating the entire purpose of the pattern
Fix
Always declare the factory method's return type as the interface (PaymentProcessor, not CreditCardProcessor). If the calling code only sees the interface, it physically cannot call implementation-specific methods.
×

Putting business logic inside the factory

Symptom
The factory grows to hundreds of lines, starts importing service classes, and becomes a bottleneck that's hard to test and impossible to reuse
Fix
The factory's only responsibility is deciding which class to instantiate and returning it. Zero business logic. If you need conditional logic beyond 'which class do I create', that logic belongs in the object itself or in a separate service, not in the factory.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
What is the difference between the Factory Method Pattern and the Abstra...
Q02SENIOR
How does the Factory Pattern relate to the Open/Closed Principle? Can yo...
Q03SENIOR
If a factory method needs to create objects that require expensive initi...
Q01 of 03SENIOR

What is the difference between the Factory Method Pattern and the Abstract Factory Pattern, and when would you choose one over the other?

ANSWER
The Factory Method Pattern provides a single method (usually static) that creates one type of object based on a parameter. The Abstract Factory Pattern defines an interface for creating families of related objects, ensuring consistency across those objects. Choose Factory Method when you need to create a single product that varies by a simple condition (e.g., payment processor by type). Choose Abstract Factory when you have multiple products that must be used together and must be consistent (e.g., UI components for a specific OS theme).
FAQ · 3 QUESTIONS

Frequently Asked Questions

01
What is the Factory Pattern in Java and why is it used?
02
Is the Factory Pattern the same as the Factory Method Pattern?
03
Does using a Factory Pattern make unit testing harder?
N
Naren Founder & Principal Engineer

20+ years shipping production Java in banking & fintech. Drawn from code that ran under real load.

Follow
Verified
production tested
May 24, 2026
last updated
1,554
articles · all by Naren
🔥

That's Advanced Java. Mark it forged?

15 min read · try the examples if you haven't

Previous
Singleton Pattern in Java
9 / 28 · Advanced Java
Next
Builder Pattern in Java