Intent
Define an interface for creating objects but let subclasses decide which class to instantiate. The client works with the product interface — completely decoupled from the concrete class.
The trigger: You need to create objects but the exact class depends on runtime input. Without Factory: a giant if-else / switch in your service that touches every concrete class. With Factory: each new type = one new class. Existing code unchanged. Open/Closed Principle.
Spring's idiomatic Factory Method: Declare an interface, annotate all implementations with @Service, inject List<PaymentProcessor> — Spring auto-discovers and injects all implementations. Pick the right one at runtime via stream().filter(). Adding a new payment type = add one @Service class. Zero changes anywhere else.
JavaPayment Processor Factory — Full E-Commerce Example
// STEP 1: Product Interface
public interface PaymentProcessor {
PaymentResult process(PaymentRequest request);
boolean supports(PaymentMethod method);
String getProviderName();
default BigDecimal calculateFee(BigDecimal amt) { return BigDecimal.ZERO; }
}
// STEP 2: Concrete Products — one class per payment provider
@Service
public class RazorpayProcessor implements PaymentProcessor {
private final RazorpayClient client;
@Override
public PaymentResult process(PaymentRequest req) {
try {
Map<String, Object> params = Map.of(
"amount", req.getAmountInPaise(), // Razorpay uses paise (₹×100)
"currency", req.getCurrency(),
"receipt", req.getOrderId().toString()
);
Order rzpOrder = client.orders().create(params);
return PaymentResult.builder()
.transactionId(rzpOrder.get("id"))
.status(PaymentStatus.PENDING)
.build();
} catch (RazorpayException e) {
throw new PaymentProcessingException("Razorpay: " + e.getMessage(), e);
}
}
@Override
public boolean supports(PaymentMethod m) {
return Set.of(PaymentMethod.CARD, PaymentMethod.UPI, PaymentMethod.NETBANKING).contains(m);
}
@Override public String getProviderName() { return "RAZORPAY"; }
@Override public BigDecimal calculateFee(BigDecimal a) { return a.multiply(BigDecimal.valueOf(0.02)); }
}
@Service
public class CodProcessor implements PaymentProcessor {
@Override
public PaymentResult process(PaymentRequest req) {
return PaymentResult.builder()
.transactionId("COD-" + req.getOrderId())
.status(PaymentStatus.PENDING_COLLECTION).build();
}
@Override public boolean supports(PaymentMethod m) { return m == PaymentMethod.COD; }
@Override public String getProviderName() { return "CASH_ON_DELIVERY"; }
}
// STEP 3: Factory — Spring injects ALL PaymentProcessor beans automatically
@Component
public class PaymentProcessorFactory {
private final List<PaymentProcessor> processors; // Spring auto-discovers all impls
public PaymentProcessorFactory(List<PaymentProcessor> processors) {
this.processors = processors;
log.info("Loaded processors: {}", processors.stream().map(PaymentProcessor::getProviderName).toList());
}
public PaymentProcessor getProcessor(PaymentMethod method) {
return processors.stream()
.filter(p -> p.supports(method))
.findFirst()
.orElseThrow(() -> new UnsupportedPaymentMethodException("No processor for: " + method));
}
// Pick cheapest when multiple processors support same method
public PaymentProcessor getCheapest(PaymentMethod m, BigDecimal amt) {
return processors.stream().filter(p -> p.supports(m))
.min(Comparator.comparing(p -> p.calculateFee(amt))).orElseThrow();
}
}
// STEP 4: Client — zero knowledge of concrete classes
@Service @RequiredArgsConstructor
public class CheckoutService {
private final PaymentProcessorFactory factory;
@Transactional
public CheckoutResult checkout(Order order, PaymentMethod method) {
PaymentProcessor processor = factory.getProcessor(method);
PaymentResult result = processor.process(PaymentRequest.from(order));
return CheckoutResult.success(order, result);
// Adding CryptoProcessor = 1 new @Service class. Zero changes here. ✅
}
}
JavaSpring FactoryBean<T> — Framework's Official Factory Extension
// Spring FactoryBean: used internally for SqlSessionFactoryBean, LocalEntityManagerFactoryBean, etc.
// Lets you customize complex bean creation. Spring calls getObject() once and caches it.
@Component
public class SnowflakeDataSourceFactoryBean implements FactoryBean<DataSource> {
@Value("${snowflake.url}") private String url;
@Value("${snowflake.warehouse}") private String warehouse;
@Override
public DataSource getObject() { // Spring calls once, result is cached
HikariConfig cfg = new HikariConfig();
cfg.setJdbcUrl(url);
cfg.addDataSourceProperty("warehouse", warehouse);
cfg.setMaximumPoolSize(10);
cfg.setConnectionTestQuery("SELECT 1");
return new HikariDataSource(cfg);
}
@Override public Class<?> getObjectType() { return DataSource.class; }
@Override public boolean isSingleton() { return true; }
}
// @Autowired DataSource ds; → gets the DataSource (product)
// @Autowired SnowflakeDataSourceFactoryBean fb; → gets the factory (prefix with &)
🎯 Interview Questions
Factory vs Abstract Factory vs Builder — when to use each in microservices?
Factory Method: Create ONE product type where the concrete class varies at runtime. Trigger: common interface, multiple impls, selected by a key. Examples: payment processor selection, notification sender (Email/SMS/Push), auth strategy (JWT/OAuth/SAML).
Abstract Factory: Create a FAMILY of 2+ related products that must be compatible. Trigger: products from different families must never be mixed. Examples: cloud provider families (AWS S3+SQS vs Azure Blob+ServiceBus), environment families (Production services vs Test stubs).
Builder: Create one complex object with many optional fields step by step. Trigger: 4+ parameters, optional fields, invariants across fields, immutability needed.
Why does Factory matter? Why not just call new directly?
Calling new RazorpayProcessor() directly: hard-codes the concrete class at every call site. Every place must import and know about all implementations. Adding a new type requires scanning every creation site. Factory: call sites only know the interface. Factory is the ONE place knowing concrete types — one change, all call sites benefit. In Spring: add a new @Service implementing the interface — Spring auto-discovers it, factory gets it injected. Zero changes to call sites.