The Factory Pattern Explained (with Java and Spring Examples)


1. What is the Factory Pattern?
The Factory Pattern is a creational pattern that delegates object creation to a separate component (a “factory”) instead of using new
directly. This helps you decouple object creation logic from the rest of your code.
🔤 Basic Example (using if-else)
interface Shape {
void draw();
}
class Circle implements Shape {
public void draw() { System.out.println("Drawing Circle"); }
}
class Square implements Shape {
public void draw() { System.out.println("Drawing Square"); }
}
class ShapeFactory {
public Shape create(String type) {
if ("circle".equals(type)) return new Circle();
if ("square".equals(type)) return new Square();
throw new IllegalArgumentException("Unknown type: " + type);
}
}
// ✅ Using the factory
ShapeFactory factory = new ShapeFactory();
Shape shape = factory.create("circle");
shape.draw(); // Output: Drawing Circle
This simple example shows the key idea:
Instead of calling
new Circle()
directly, the client asks the factory to create the object.
2. Why Use It?
To hide complex instantiation logic
To choose an implementation at runtime
To follow the Open-Closed Principle — adding new types without modifying client code
3. Isn’t if-else
a violation of Open-Closed?
Yes. A factory like this:
if ("circle".equals(type)) return new Circle();
if ("square".equals(type)) return new Square();
…requires modifying code to add new types — a violation of the Open-Closed Principle.
✅ Instead, use a Map<String, Supplier<Shape>>
:
Map<String, Supplier<Shape>> registry = Map.of(
"circle", Circle::new,
"square", Square::new
);
Or an enum
to encapsulate creation:
public enum ShapeType {
CIRCLE(() -> new Circle()),
SQUARE(() -> new Square());
private final Supplier<Shape> constructor;
ShapeType(Supplier<Shape> c) { this.constructor = c; }
public Shape create() { return constructor.get(); }
}
⚠️ Common Gotcha: new Circle()
vs Circle::new
Don’t confuse storing a constructor with storing an instance.
map.put("circle", new Circle()); // ❌ one shared object
map.put("circle", Circle::new); // ✅ a new object each time you call .get()
Using Supplier<Shape>
lets you store a constructor reference, so the factory creates a new object every time — which is exactly what you want in most cases.
4. Does Spring Replace the Factory Pattern?
Most of the time, yes.
Spring is a Dependency Injection container — it creates, wires, and manages object lifecycles for you. In many applications, this eliminates the need to write factories manually.
You can typically combine the idea of selecting a runtime implementation of a given interface while still delegating object creation to the Spring Framework.
For example, consider the following setup:
✅ Registering multiple implementations in Spring
Suppose you have different implementations of an interface:
public interface FileParser {
void parse(String content);
}
You can register each one as a named Spring bean:
@Component("json")
public class JsonParser implements FileParser { ... }
@Component("xml")
public class XmlParser implements FileParser { ... }
Now, Spring can inject all available FileParser
beans into a map:
@Component
public class ParserFactory {
private final Map<String, FileParser> parsers;
public ParserFactory(Map<String, FileParser> parsers) {
this.parsers = parsers;
}
public FileParser getParser(String type) {
FileParser parser = parsers.get(type.toLowerCase());
if (parser == null) {
throw new IllegalArgumentException("Unsupported file type: " + type);
}
return parser;
}
}
✅ Usage example
String fileType = request.getParam("type"); // User input
FileParser parser = parserFactory.getParser(fileType);
parser.parse(content);
This pattern lets you keep all objects managed by Spring, while still choosing the right one at runtime.
5. Disadvantages of the Factory Pattern
Like all patterns, it comes with trade-offs:
Indirection: More layers = more cognitive overhead for simple cases
Still needs registration: Adding new types usually requires registering them somewhere (e.g., in a map or enum)
Harder debugging: Errors during instantiation may be less obvious
Overuse
6. Conclusion
The Factory Pattern helps you encapsulate logic to select the right implementation of an interface. Use it when dealing with multiple implementations, dynamic decisions, or complex object creation.
It’s simple and effective, especially when combined with Dependency Injection frameworks like Spring.
Use it consciously to keep your design flexible, while being mindful of trade-offs like added indirection and registration overhead.
Subscribe to my newsletter
Read articles from Mirna De Jesus Cambero directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Mirna De Jesus Cambero
Mirna De Jesus Cambero
I’m a backend software engineer with over a decade of experience primarily in Java. I started this blog to share what I’ve learned in a simplified, approachable way — and to add value for fellow developers. Though I’m an introvert, I’ve chosen to put myself out there to encourage more women to explore and thrive in tech. I believe that by sharing what we know, we learn twice as much — that’s precisely why I’m here.