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.
The Problem It Solves
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.
Here is what changes when you apply the Factory Method pattern.
Structure
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.
Implementation
I reach for the classic factory method when I need subclass polymorphism. But most production code uses the simpler variants, so I will show all three.
The key question is: how often do new product types appear? If rarely, simple factory is fine. If frequently, go registry. If you want to leverage framework extension points (like Spring bean definitions), the classic subclass approach has its place.
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. In practice, a static method with a switch covers 90% of real-world cases. I reach for this when my 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 factory. The mistake I see most often is teams sticking with a simple factory even when their type set grows every quarter.
// 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. I use this in tests and prototypes where a full class feels heavy.
// 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.
In an interview, mentioning the lambda approach shows you understand that factories and functional interfaces are two sides of the same coin. A Supplier<Message> is a factory with one method.
How It Works
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 is the same flow whether you use the classic factory method (subclass) or a registry (map lookup). The critical property is that the client never participates in the creation decision. In an interview, draw this sequence first, then explain why the indirection is worth it: testability, extensibility, and single-responsibility for the creation logic.
Real-World Examples
You will find factory methods everywhere in the Java standard library and Spring ecosystem. Recognizing them in existing code helps you explain the pattern with confidence in interviews.
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.
When to Use / 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 you have multiple types that vary at runtime, you need a factory. If you have one type with a simple constructor, new is fine.
Interview progression
When an interviewer asks "how would you add a new notification channel?", start with the simple factory. If they push on Open/Closed, upgrade to the registry. If they ask about families of related objects, pivot to abstract factory. Show the thinking, not just the answer.
Common Mistakes in Interviews
-
Confusing Simple Factory with Factory Method. Simple factory is a static method with a
switch. Factory method uses subclass inheritance where each creator overrides a method. Say: "A simple factory centralizes creation in one place. The factory method pattern delegates creation to subclasses through inheritance." -
Explaining WHAT but not WHY. Candidates describe the structure perfectly but never say why they chose it. Always connect to a requirement: "I used factory method here because the notification channel is determined by user preferences at runtime, and I want to add channels without modifying existing sender logic."
-
Over-engineering for a single type. If your system has exactly one
Messageimplementation and no plan for others, a factory adds complexity for no benefit. Say: "A factory is worth it when you have or expect multiple product types. For a single type, direct construction is simpler." -
Forgetting the Registry variant. Many interviewers expect you to evolve from simple factory to registry when they push on Open/Closed. Show the progression: "The simple factory works for stable types. If extensibility matters, I upgrade to a registry of
Supplier<Message>that new types register into at startup." -
Not connecting Factory to Dependency Injection. Factory method is fundamentally about DI. The caller depends on
Message(an abstraction), notEmailMessage(a concrete class). Say: "Factory method is the manual version of what Spring does with@Bean."
In interviews, I find the biggest differentiator is whether candidates can explain the progression: "I would start with a simple factory for stable types, upgrade to registry for extensibility, and reach for abstract factory if I need compatible product families."
Choosing the Right Factory Flavor
This decision flowchart is the one I use in interviews. Start with "how many product families?" and follow the arrows. If the interviewer pushes, show the comparison table below.
| 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: most real projects land in the "Simple Factory" or "Registry" column. The full Factory Method pattern (with subclass inheritance) is primarily a framework pattern, not an application pattern.
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 static method with a
switchand covers 90% of real-world cases where 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 is the manual version of what Spring's DI container does with
@Bean. If you already use Spring, you rarely write factory methods by hand. - In an LLD interview, name the pattern explicitly, show the interface, explain why variation exists, and demonstrate the progression from simple factory to registry when pushed on extensibility.