Factory Method Design Pattern in Java
Learn Factory Method Design Pattern in Java with purpose, diagrams, code examples, input/output, real-time examples, framework usage, benefits, limitations, and interview questions.
Introduction
Factory Method is a Creational Design Pattern used to create objects without exposing the object creation logic to the client.
Instead of creating objects directly using new, the client asks a factory method to create the object.
This makes the code more flexible, maintainable, and easier to extend.
Purpose of Factory Method Pattern
The main purpose of Factory Method is:
Create objects through a common method instead of directly using new.
It helps when object creation depends on input, condition, configuration, or business rules.
Real-Time Analogy
Think about a restaurant.
A customer orders food:
Pizza
Burger
Pasta
The customer does not know how the kitchen prepares each item.
The customer only places an order.
The kitchen creates the requested food.
Customer → Order → Kitchen Factory → Food Item
Similarly:
Client → Request Type → Factory Method → Object
Problem Without Factory Method
Suppose we create notification objects directly.
public class NotificationService {
public void sendNotification(String type, String message) {
if (type.equals("EMAIL")) {
EmailNotification email = new EmailNotification();
email.send(message);
} else if (type.equals("SMS")) {
SmsNotification sms = new SmsNotification();
sms.send(message);
} else if (type.equals("PUSH")) {
PushNotification push = new PushNotification();
push.send(message);
}
}
}
Problems in This Code
- Object creation logic is mixed with business logic
- Code becomes hard to maintain
- Adding a new notification type requires changing existing service code
- Violates Open/Closed Principle
- Too many
if-elseconditions
Factory Method Solution
Factory Method moves object creation logic into a separate factory class.
flowchart LR
A[Client]
A --> B[NotificationFactory]
B --> C{Notification Type}
C -->|EMAIL| D[EmailNotification]
C -->|SMS| E[SmsNotification]
C -->|PUSH| F[PushNotification]
Factory Method Class Diagram
classDiagram
class Notification {
<<interface>>
+send(message)
}
class EmailNotification {
+send(message)
}
class SmsNotification {
+send(message)
}
class PushNotification {
+send(message)
}
class NotificationFactory {
+createNotification(type) Notification
}
Notification <|.. EmailNotification
Notification <|.. SmsNotification
Notification <|.. PushNotification
NotificationFactory --> Notification
Step 1: Create Common Interface
public interface Notification {
void send(String message);
}
Step 2: Create Concrete Classes
public class EmailNotification implements Notification {
@Override
public void send(String message) {
System.out.println("Email Notification Sent: " + message);
}
}
public class SmsNotification implements Notification {
@Override
public void send(String message) {
System.out.println("SMS Notification Sent: " + message);
}
}
public class PushNotification implements Notification {
@Override
public void send(String message) {
System.out.println("Push Notification Sent: " + message);
}
}
Step 3: Create Factory Class
public class NotificationFactory {
public static Notification createNotification(String type) {
if (type == null || type.isBlank()) {
throw new IllegalArgumentException("Notification type is required");
}
return switch (type.toUpperCase()) {
case "EMAIL" -> new EmailNotification();
case "SMS" -> new SmsNotification();
case "PUSH" -> new PushNotification();
default -> throw new IllegalArgumentException("Invalid notification type: " + type);
};
}
}
Step 4: Client Code
public class FactoryMethodDemo {
public static void main(String[] args) {
Notification email =
NotificationFactory.createNotification("EMAIL");
email.send("Welcome to CodeWithVenu");
Notification sms =
NotificationFactory.createNotification("SMS");
sms.send("Your OTP is 123456");
Notification push =
NotificationFactory.createNotification("PUSH");
push.send("New login detected");
}
}
Output
Email Notification Sent: Welcome to CodeWithVenu
SMS Notification Sent: Your OTP is 123456
Push Notification Sent: New login detected
Output Explanation
The client passes the notification type.
Factory creates the correct object.
Client uses the common interface.
The client does not know the concrete class creation details.
Object Creation Flow
sequenceDiagram
participant Client
participant Factory
participant Email
participant SMS
participant Push
Client->>Factory: createNotification("EMAIL")
Factory->>Email: new EmailNotification()
Email-->>Factory: Email Object
Factory-->>Client: Notification
Client->>Factory: createNotification("SMS")
Factory->>SMS: new SmsNotification()
SMS-->>Factory: SMS Object
Factory-->>Client: Notification
Real-Time Example: Payment Processing
In enterprise applications, payment method may differ.
Examples:
- Credit Card
- Debit Card
- UPI
- PayPal
Factory Method can create the correct payment processor.
Payment Interface
public interface PaymentProcessor {
void pay(double amount);
}
Payment Implementations
public class CreditCardPayment implements PaymentProcessor {
@Override
public void pay(double amount) {
System.out.println("Paid $" + amount + " using Credit Card");
}
}
public class PayPalPayment implements PaymentProcessor {
@Override
public void pay(double amount) {
System.out.println("Paid $" + amount + " using PayPal");
}
}
public class UpiPayment implements PaymentProcessor {
@Override
public void pay(double amount) {
System.out.println("Paid $" + amount + " using UPI");
}
}
Payment Factory
public class PaymentFactory {
public static PaymentProcessor getPaymentProcessor(String paymentType) {
return switch (paymentType.toUpperCase()) {
case "CREDIT_CARD" -> new CreditCardPayment();
case "PAYPAL" -> new PayPalPayment();
case "UPI" -> new UpiPayment();
default -> throw new IllegalArgumentException("Unsupported payment type");
};
}
}
Payment Client
public class PaymentDemo {
public static void main(String[] args) {
PaymentProcessor processor =
PaymentFactory.getPaymentProcessor("CREDIT_CARD");
processor.pay(250.00);
}
}
Output
Paid $250.0 using Credit Card
Enterprise Use Cases
Factory Method is commonly used in:
- Notification Services
- Payment Gateways
- Report Generation
- File Parsers
- Database Connectors
- Cloud Provider Clients
- Message Queue Producers
- Authentication Providers
Report Generation Example
flowchart TD
A[Report Request]
A --> B[ReportFactory]
B --> C{Report Type}
C -->|PDF| D[PDF Report]
C -->|EXCEL| E[Excel Report]
C -->|CSV| F[CSV Report]
Microservices Example
In microservices, Factory Method can create integration clients.
Example:
Payment Service needs to call different providers:
Stripe
PayPal
Square
Razorpay
Factory creates the correct provider client based on configuration.
Spring Boot Framework Example
Spring can replace manual factory logic using Dependency Injection.
public interface PaymentService {
void pay(double amount);
}
@Service("creditCardPayment")
public class CreditCardPaymentService implements PaymentService {
@Override
public void pay(double amount) {
System.out.println("Credit Card Payment: " + amount);
}
}
@Service("paypalPayment")
public class PayPalPaymentService implements PaymentService {
@Override
public void pay(double amount) {
System.out.println("PayPal Payment: " + amount);
}
}
Spring Factory Using ApplicationContext
@Component
public class PaymentServiceFactory {
private final ApplicationContext applicationContext;
public PaymentServiceFactory(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public PaymentService getPaymentService(String type) {
return switch (type.toUpperCase()) {
case "CREDIT_CARD" -> applicationContext.getBean("creditCardPayment", PaymentService.class);
case "PAYPAL" -> applicationContext.getBean("paypalPayment", PaymentService.class);
default -> throw new IllegalArgumentException("Invalid payment type");
};
}
}
Spring Controller Example
@RestController
@RequestMapping("/payments")
public class PaymentController {
private final PaymentServiceFactory factory;
public PaymentController(PaymentServiceFactory factory) {
this.factory = factory;
}
@PostMapping("/{type}")
public String pay(@PathVariable String type) {
PaymentService paymentService =
factory.getPaymentService(type);
paymentService.pay(500.00);
return "Payment completed using " + type;
}
}
API Input
POST /payments/CREDIT_CARD
API Output
Payment completed using CREDIT_CARD
Better Spring Approach: Map-Based Factory
Instead of switch-case, use a map.
@Component
public class PaymentFactory {
private final Map<String, PaymentService> paymentServices;
public PaymentFactory(Map<String, PaymentService> paymentServices) {
this.paymentServices = paymentServices;
}
public PaymentService getPaymentService(String type) {
PaymentService service = paymentServices.get(type);
if (service == null) {
throw new IllegalArgumentException("Unsupported payment type: " + type);
}
return service;
}
}
Map-Based Bean Names
@Service("creditCard")
public class CreditCardPaymentService implements PaymentService {
public void pay(double amount) {
System.out.println("Credit Card Payment: " + amount);
}
}
@Service("paypal")
public class PayPalPaymentService implements PaymentService {
public void pay(double amount) {
System.out.println("PayPal Payment: " + amount);
}
}
Input
creditCard
Output
Credit Card Payment: 500.0
Benefits of Factory Method
- Hides object creation logic
- Reduces tight coupling
- Improves maintainability
- Supports Open/Closed Principle
- Makes code easier to test
- Useful when object creation is conditional
- Centralizes creation logic
Limitations
- Adds extra classes
- Can become complex with too many types
- Simple object creation may not need factory
- Switch-case factories still require modification when new type is added
When to Use Factory Method
Use Factory Method when:
- Object creation depends on input
- Multiple implementations exist
- Client should not know concrete classes
- You want loose coupling
- You want reusable creation logic
When Not to Use Factory Method
Avoid Factory Method when:
- There is only one implementation
- Object creation is very simple
- Factory only adds unnecessary complexity
Factory Method vs Singleton
| Feature | Singleton | Factory Method |
|---|---|---|
| Purpose | One instance | Object creation |
| Pattern Type | Creational | Creational |
| Focus | Reuse same object | Create correct object |
| Example | Logger | Payment Processor |
Interview Questions
What is Factory Method Pattern?
Factory Method is a creational pattern that creates objects without exposing object creation logic to the client.
Why use Factory Method?
To reduce coupling and centralize object creation.
Is Factory Method same as Simple Factory?
No. Simple Factory usually uses one factory class with conditions. Factory Method can be extended using inheritance or polymorphism.
Where is Factory Method used in Java?
Examples:
Calendar.getInstance()NumberFormat.getInstance()DriverManager.getConnection()- Spring Bean Factory
How is it used in Spring?
Spring creates and injects required bean implementations using Dependency Injection and BeanFactory/ApplicationContext.
Key Takeaways
- Factory Method is a Creational Design Pattern.
- It hides object creation logic.
- It returns objects through a common interface.
- It reduces coupling between client and concrete classes.
- It is useful for notifications, payments, reports, parsers, and providers.
- Spring Dependency Injection acts like an advanced factory mechanism.