Full Stack • Java • System Design • Cloud • AI Engineering

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-else conditions

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.