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.
20+ years shipping production Java in banking & fintech. Drawn from code that ran under real load.
- 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
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.
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., and Spring’s NumberFormat.getNumberInstance()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 method returns objects that are already wired with their dependencies. The pattern doesn’t eliminate create()new—it just moves it behind an abstraction layer that you can test, extend, and maintain without touching client code.
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.
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 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.CreditCardProcessor()
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'.
// 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."); } }
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().
someOperation() method typically calls factoryMethod() and works with the returned Product through polymorphism.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:
- 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.
- 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.
- 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.
- You anticipate adding new product types frequently. Factories make it easy to introduce new implementations by adding a single case without touching existing callers.
- 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.
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.
// ─── 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); } }
Abstract Factory — When You Need Families of Related Objects
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.
// ─── 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(); } }
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.
// ─── 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.
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.
// ─── 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.
PaymentProcessorFactory.create()) cannot be mocked easily. If you need testability, make the factory an interface and inject it. Your unit tests will thank you.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 directly. GregorianCalendar()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 method in Java, ask yourself: 'What is this hiding from me, and why?'create()
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); } }
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.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:
| Aspect | Simple Factory | Factory Method | Abstract Factory |
|---|---|---|---|
| Purpose | Centralise creation of a single product type | Delegate creation to subclasses, create one product type | Create families of related products |
| Structure | Single static/instance method with a switch | Abstract Creator with factoryMethod() overridden by subclasses | Interface with multiple creation methods, each returning a product type |
| Flexibility | Low — hard-coded mapping in one method | Medium — subclasses can decide product | High — can swap entire families |
| Coupling | Client depends on factory, product interface | Client depends on Creator abstract class | Client depends on AbstractFactory interface |
| Extensibility | Add case in factory method | Add new ConcreteCreator subclass | Add new ConcreteFactory (must implement all methods) |
| Complexity | Low | Medium | High |
| When to use | Simple object creation varying by one parameter | Framework where subclasses define products | When 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.
Factory Pattern — Pros and Cons
No pattern is a silver bullet. Here are the trade-offs to consider when adopting the Factory Pattern:
| Pros | Cons |
|---|---|
| Decoupling — Client code depends on interfaces, not concrete classes | Increased complexity — Adds extra classes and indirection |
| Open/Closed Principle — New products can be added without modifying existing callers | May introduce unnecessary abstraction — Overuse for single-product scenarios |
| Testability — Easy to mock factories and swap implementations in tests | More classes to maintain — Each new product needs a new class and factory update |
| Centralised creation logic — Changes to object construction happen in one place | Can become a God object — If the factory handles too many types, it grows unwieldy |
| Reusable — Same factory can be used across multiple clients | Not 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 details | Static 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.
new call.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.
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 scattered across your codebase, you've introduced a compile-time dependency on a concrete class. Change that to GasolineEngine()new 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.ElectricMotor()
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.
// 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(); } }
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.
// 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(); } }
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.
// 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
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.'
// 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(); }; } }
Payment Factory Returns Null After Config Migration
PaymentProcessorFactory.create(). The stack trace points to a switch expression returning null.- 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.
System.err.println("Factory input: " + type);Add default case in switch: default -> throw new IllegalArgumentException("Unknown type: " + type);Optional.ofNullable() and handle absent caseSystem.out.println("Created: " + result.getClass().getName());Verify the mapping logic in the factory methodgrep -r 'PAYMENT_TYPE' src/main/java/**/factory/Verify that the enum and factory switch are in the same module| Aspect | Factory Method | Abstract Factory |
|---|---|---|
| Purpose | Create one type of product | Create families of related products |
| Structure | Single factory class with one create() method | Factory interface with one method per product type |
| When to use | Object creation logic varies by a single parameter | Multiple related objects must be consistent with each other |
| Adding new product types | Add a new case to the factory switch/if | Add a new concrete factory class implementing all methods |
| Complexity | Low — easy to start with | Higher — more classes, but more flexible |
| Real-world Java example | Calendar.getInstance() | Spring ApplicationContext (bean factory) |
| Coupling to concrete classes | Isolated to factory class only | Isolated to concrete factory classes only |
| Client code changes when adding products | None — just update the factory | None — just add a new concrete factory |
Key takeaways
Common mistakes to avoid
3 patternsUsing raw String keys in the factory switch statement
Returning a concrete class type from the factory method instead of the interface
CreditCardProcessor.setCardNumber()), tightly coupling it to one implementation and defeating the entire purpose of the patternPutting business logic inside the factory
Interview Questions on This Topic
What is the difference between the Factory Method Pattern and the Abstract Factory Pattern, and when would you choose one over the other?
How does the Factory Pattern relate to the Open/Closed Principle? Can you give a concrete example where adding a new type requires zero changes to existing calling code?
If a factory method needs to create objects that require expensive initialisation — like a database connection — how would you prevent the factory from creating a new object on every call? (Follow-up probes knowledge of combining Factory with Singleton or object pooling.)
Frequently Asked Questions
The Factory Pattern is a creational design pattern that delegates object creation to a dedicated factory class or method, instead of using new directly throughout your code. It's used to decouple calling code from concrete class names, making it easy to add new types or swap implementations without changing any code that uses those objects.
Not quite. 'Factory Pattern' is an informal term often used to describe a simple static factory class. The 'Factory Method Pattern' is a formal GoF pattern where a method in a class (or interface) is responsible for creating objects, and subclasses can override that method to change what gets created. Abstract Factory is a third, related pattern for creating families of objects. In practice, most teams use 'factory' loosely to mean any class whose job is creating other objects.
The opposite, actually. Because your calling code depends on an interface rather than a concrete class, you can easily inject a mock or test implementation during unit tests. Without the factory (i.e., with new inline), you can't swap in a test double without modifying the class under test. The factory pattern, especially when combined with dependency injection, makes classes significantly more testable.ConcreteClass()
20+ years shipping production Java in banking & fintech. Drawn from code that ran under real load.
That's Advanced Java. Mark it forged?
15 min read · try the examples if you haven't