Factory method pattern
The factory method replaces direct constructor calls with a method that subclasses can override to return different types. Callers depend on an abstraction, not a concrete class.
Introduction
The Factory Method pattern moves product construction behind a method that concrete creators can override. Client code depends on a product abstraction, while the creator hierarchy decides which concrete product to instantiate.
TL;DR / mental model: Let the caller ask for a capability, not a concrete class. A factory method supplies the right product, and the caller uses the shared product interface.
Problem and Context
Picture this: your notification service works great for email. Then product asks for SMS. Then push notifications. Each time, you crack open the same class, add another if branch, and pray you didn't break the email path.
This violates Open/Closed. Every new channel forces you to modify NotificationService, retest every branch, and risk breaking existing channels. The factory method fixes this by moving the "which type to create?" decision behind an interface.
When It Helps
Factory Method helps when the product type varies and the surrounding workflow should stay the same, or when subclasses or plugins need to provide different products. For a small, stable set of types, a simple factory or direct construction may be clearer.
Here is what changes when you apply the Factory Method pattern.
Participants and Structure
Participants
- Product: the common interface,
Message, used by client code. - Concrete products:
EmailMessage,SmsMessage, andPushMessage, each implementing channel-specific behavior. - Creator:
NotificationSender, which declares the factory method and the workflow that uses its result. - Concrete creators: subclasses such as
EmailSenderandSmsSender, which choose the concrete product. - Client: works with the creator and product abstractions rather than constructing concrete messages directly.
NotificationSender defines the template: call createMessage(), then deliver() on the result. Each concrete sender overrides createMessage() to return a different product. The client works with NotificationSender and never touches concrete message classes directly.
Idiomatic Example and Implementation Notes
The classic factory method is useful when subclass polymorphism is part of the extension point. Many application codebases instead use a simple factory or registry, so the variants below are compared explicitly.
The key question is how the product set changes. If types are few and stable, a simple factory may be enough. If types are added through configuration or plugins, a registry can make that extension explicit. If subclasses are already the extension point, the classic form fits naturally.
Three Flavors of Factory
Factory Method uses subclass inheritance. Simple Factory uses a static method with a switch. Registry uses a map of suppliers. Most production code uses the simple factory or registry.
// Product interface: every notification channel implements this.
// The factory never exposes which concrete class it created.
public interface Message {
void deliver(String to, String body);
}Simple Factory (the shortcut most teams actually use)
The classic factory method requires one subclass per product type. A static method with a switch is often simpler when the product types are stable and few.
// Simple factory: one static method decides which type to create.
// Trade-off: violates Open/Closed (modify this for new types)
// but far simpler when types are stable.
public class MessageFactory {
public static Message create(String channel) {
return switch (channel) {
case "email" -> new EmailMessage();
case "sms" -> new SmsMessage();
case "push" -> new PushMessage();
default -> throw new IllegalArgumentException(
"Unknown channel: " + channel);
};
}
}
Registry Approach (Open/Closed compliant)
When new types appear frequently (plugin systems, multi-tenant configs), a registry avoids modifying the central creation method. It is useful when the registration boundary is explicit and can be validated at startup.
// Registry: types register themselves at startup.
// Adding a new channel never touches this class.
public class MessageRegistry {
private final Map<String, Supplier<Message>> creators = new HashMap<>();
public void register(String channel, Supplier<Message> creator) {
creators.put(channel, creator);
}
public Message create(String channel) {
Supplier<Message> creator = creators.get(channel);
if (creator == null) {
throw new IllegalArgumentException(
"No registered creator for: " + channel);
}
return creator.get();
}
}
// At application startup:
MessageRegistry registry = new MessageRegistry();
registry.register("email", EmailMessage::new);
registry.register("sms", SmsMessage::new);
registry.register("push", PushMessage::new);
// New channel? registry.register("slack", SlackMessage::new)
Lambda Shortcut (Java 8+)
Since Message has a single abstract method, you can use lambdas as inline factories. This is useful in tests and small configuration-driven cases where a full class would add noise.
// Lambda as a factory: no class file needed
Supplier<Message> urgentEmail = () -> new EmailMessage();
// Or even inline in test code
NotificationSender sender = new NotificationSender() {
@Override
protected Message createMessage() {
return new Message() {
@Override
public void deliver(String to, String body) {
System.out.println("[Test] " + to + ": " + body);
}
};
}
};
For production code with real dependencies (SMTP config, API keys), dedicated classes are cleaner. Lambdas shine for simple testing scenarios and config-driven one-offs.
A Supplier<Message> is a factory with one method, so functional interfaces can replace a dedicated factory type when the creation contract is small.
Step-by-Step Behavior
The runtime interaction shows why the caller stays decoupled. At no point does the client reference a concrete message class.
- Client calls
send()on aNotificationSenderreference. It does not know (or care) that this is anEmailSender. send()callscreateMessage(), the factory method. Since the runtime type isEmailSender, this returns anEmailMessage.send()callsdeliver()on the returnedMessage. The client never imported or referencedEmailMessagedirectly.- Swapping channels means injecting a different
NotificationSendersubclass at the composition root. Zero changes to calling code.
This flow is the same whether creation uses subclass polymorphism or a registry lookup. The important property is that the client consumes the product abstraction instead of constructing a concrete product and embedding its creation details in the workflow.
Real-World Examples
Factory methods appear throughout the Java standard library and Spring ecosystem. Recognizing them in existing code helps identify where construction has been separated from use.
java.util.Calendar.getInstance()returns aGregorianCalendar,BuddhistCalendar, orJapaneseImperialCalendardepending on locale. Callers work with theCalendarinterface and never name a concrete class.- Spring's
BeanFactoryis a factory method on steroids.getBean("userService")returns whatever concrete class is registered under that name. Your code depends on interfaces; Spring decides the implementation. java.sql.DriverManager.getConnection(url)picks the right JDBC driver based on the URL prefix (jdbc:mysql:,jdbc:postgresql:). You call one static method and get back aConnectionfor the correct database.java.util.Collectionsutility methods likeunmodifiableList()andsynchronizedList()are factories that wrap existing collections in decorator implementations. You call a factory method and get back aListwith different behavior.
Trade-offs, Alternatives, and When Not to Use
Use when:
- The exact class to instantiate is determined at runtime (config, user input, environment)
- You need to isolate object creation for testability (inject a mock factory in tests)
- Your system is pluggable and new product types can be added without modifying existing code
- Construction involves setup logic you want to centralize (connection pooling, caching)
Skip when:
- There is exactly one implementation and no foreseeable variation
- The constructor is trivial (
new User(name, email)) with no branching logic - You are adding the pattern "just in case" for a variation that may never come
If several types vary at runtime or construction is non-trivial, a factory may reduce coupling. If there is one type with a simple constructor, new is usually clearer.
Trade-offs and alternatives: A factory adds indirection and extra types, but it can centralize construction and make product substitution easier. Use direct construction for a stable, trivial product; use a registry for runtime extension; use Abstract Factory when several related products must be created as a compatible family; and use Strategy when the variation is an algorithm rather than an object-construction decision.
A practical progression
Start with direct construction when there is no meaningful variation. Move to a simple factory for a small stable set of products, a registry when extensions are registered at runtime, and Abstract Factory when multiple related products must be selected together.
Explain It in 30 Seconds
Factory Method lets a creator defer construction to an overridable createX() method. Concrete creators return different implementations of a shared product interface, while the client uses the product without naming its concrete class. Choose a simple factory or registry when those forms match the extension needs more directly.
Explain It in 5 Minutes
Begin with the creation problem: a workflow should use a Message, but it should not contain a branch and a concrete constructor for every channel. Define the Message product interface, put the shared workflow in NotificationSender, and let EmailSender or SmsSender override createMessage(). The client receives the creator or product abstraction through composition or dependency injection. Then compare the alternatives: direct construction for one stable type, a simple factory for a small fixed set, a registry for runtime extension, and Abstract Factory for compatible product families.
Common Mistakes and Misconceptions
-
Confusing Simple Factory with Factory Method. A simple factory is often a method with a
switch; Factory Method uses creator inheritance where each subclass overrides a method. One centralizes the decision, while the other delegates it to subclasses. -
Explaining the structure without the force. Name the variation that justifies the abstraction: the notification channel may be selected at runtime, and the sender workflow should not construct each concrete message itself.
-
Over-engineering for a single type. If there is one
Messageimplementation, construction is trivial, and no testing or extension boundary is needed, direct construction is simpler. -
Treating every factory as interchangeable. A simple factory, Factory Method, registry, and Abstract Factory solve different variation shapes. Choose the smallest form that fits the product count, extension mechanism, and number of related products.
-
Missing the Dependency Inversion connection. The caller depends on
Message(an abstraction), notEmailMessage(a concrete class). A dependency-injection container can provide the creator or product, but DI and Factory Method are related mechanisms rather than identical patterns.
The useful progression is based on the requirement: direct construction for one stable type, simple factory for a small fixed set, registry for runtime extension, and Abstract Factory for compatible product families.
Choosing the Right Factory Flavor
Start with "how many product families?" and follow the arrows. The comparison table below summarizes the trade-offs.
| Aspect | Simple Factory | Factory Method | Registry | Abstract Factory |
|---|---|---|---|---|
| Mechanism | Static method + switch | Subclass overrides method | Map of Supplier<T> | Factory interface per family |
| Open/Closed | Violated (modify on new type) | Satisfied (new subclass) | Satisfied (register at startup) | Satisfied (new factory class) |
| Complexity | Low | Medium | Medium | High |
| Products | One type, multiple variants | One type, multiple variants | One type, extensible | Multiple related types |
| Best for | Stable, small type sets | Framework extension points | Plugin systems | Cross-platform UIs |
The key takeaway: many projects land in the "Simple Factory" or "Registry" column. The full Factory Method form is especially useful when subclass inheritance is the intended extension point; it is not required for every construction problem.
Test Your Understanding
Quick Recap
- Factory method replaces hardcoded
new ConcreteType()with acreateX()method that subclasses override, decoupling the caller from the concrete product class. - Simple factory uses a method with a
switchand is often appropriate when types are stable and few. - Registry factory stores
Supplier<T>entries in a map, satisfying Open/Closed by letting new types register at startup without modifying the factory. - The core value is dependency inversion: callers depend on
Message(interface), notEmailMessage(concrete class), enabling substitution and testing. - Factory Method and dependency injection both separate use from construction, but they are not identical: a DI container supplies dependencies, while Factory Method delegates product creation to an overridable method.
- Choose the smallest factory form that matches the variation: direct construction, simple factory, registry, Factory Method, or Abstract Factory.
Related Patterns
- Abstract Factory - Creates families of related products; Factory Method usually creates one product through a creator hierarchy.
- Strategy pattern - Swaps an algorithm or policy; Factory Method decides which product to instantiate.
- Builder pattern - Constructs one complex object step by step; a factory selects or creates a product variant.
- Dependency injection pattern - Supplies collaborators from outside; a factory can be one of those collaborators.
Related Articles
How the abstract factory creates families of related objects without specifying their concrete classes, letting you swap product families by swapping the factory.
The strategy pattern extracts a family of algorithms behind an interface so the client can swap behaviors at runtime without touching the context class.