design

Strategy Pattern Using Enum + Lambda in Java

Learn how to use Enum + Lambda as a practical Strategy Pattern in Java, with real-world examples and clean, interview-friendly code.

Reading Time: 14 min readAuthor: DeepTechHub
#java#design-patterns#strategy-pattern#enum#lambda#architecture
Strategy Pattern Using Enum + Lambda in Java

The Strategy Pattern is one of the most commonly used design patterns in Java. Traditionally, it is implemented using an interface and multiple concrete classes.

However, not every strategy deserves its own class.

When the number of strategies is small, fixed, and stateless, Enum + Lambda provides a much simpler implementation while preserving the core idea of the Strategy Pattern.

This approach removes switch statements, reduces boilerplate, and keeps each strategy close to the type it belongs to.


The Problem

Suppose an application calculates payment processing fees.

A common implementation looks like this:

public BigDecimal calculateFee(PaymentMethod method, BigDecimal amount) {
 
    switch (method) {
        case CARD:
            return amount.multiply(BigDecimal.valueOf(0.02));
 
        case CASH:
            return BigDecimal.ZERO;
 
        case DIGITAL_WALLET:
            return amount.multiply(BigDecimal.valueOf(0.01));
    }
 
    throw new IllegalArgumentException("Unsupported payment method");
}

This works, but every new payment method requires modifying the same switch statement.

As more business rules are added, the method becomes increasingly difficult to maintain.

Instead of centralizing every algorithm in one place, we can move each algorithm next to the enum constant that owns it.


Enum + Lambda Solution

Each enum constant stores its own implementation using a lambda expression.

import java.math.BigDecimal;
import java.util.function.Function;
 
public enum PaymentMethod {
 
    CARD(amount -> amount.multiply(BigDecimal.valueOf(0.02))),
 
    CASH(amount -> BigDecimal.ZERO),
 
    DIGITAL_WALLET(amount -> amount.multiply(BigDecimal.valueOf(0.01)));
 
    private final Function<BigDecimal, BigDecimal> feeCalculator;
 
    PaymentMethod(Function<BigDecimal, BigDecimal> feeCalculator) {
        this.feeCalculator = feeCalculator;
    }
 
    public BigDecimal calculateFee(BigDecimal amount) {
        return feeCalculator.apply(amount);
    }
}

Using it is straightforward.

BigDecimal amount = new BigDecimal("1000");
 
BigDecimal fee =
        PaymentMethod.CARD.calculateFee(amount);
 
System.out.println(fee);

Notice what disappeared.

  • No switch
  • No if-else
  • No duplicated decision logic

Each payment method owns its own behavior.


Why This Is Still the Strategy Pattern

Some developers wonder whether Enum + Lambda is really the Strategy Pattern.

It is.

The implementation looks different, but the design principle remains exactly the same.

Traditional Strategy:

PaymentStrategy


 ┌──┴──────────────┐
 │                 │
CardStrategy   CashStrategy

Enum + Lambda:

PaymentMethod
 ├── CARD  → lambda
 ├── CASH  → lambda
 └── DIGITAL_WALLET → lambda

In both approaches:

  • multiple algorithms exist
  • only one algorithm is selected at runtime
  • client code works through a common contract
  • implementations remain independent

The only difference is where the behavior lives.

With classic Strategy, each implementation is a separate class.

With Enum + Lambda, each enum constant owns its implementation.


Another Practical Example

Notification routing is another good fit.

public enum NotificationChannel {
 
    EMAIL((user, message) -> emailClient.send(user, message)),
 
    SMS((user, message) -> smsGateway.send(user, message)),
 
    PUSH((user, message) -> pushProvider.send(user, message));
 
    // constructor omitted
}

Each channel knows how to send a notification.

The client simply selects a channel and executes it.

The routing logic stays together instead of being scattered across multiple if-else statements.


When Enum + Lambda Works Well

Choose this approach when:

  • the number of strategies is known in advance
  • strategies are relatively small
  • strategies are stateless
  • you want to minimise boilerplate
  • readability is more important than maximum flexibility

Typical use cases include:

  • payment fee calculation
  • discount policies
  • shipping charges
  • tax calculation
  • commission rules
  • notification routing
  • document formatting

When It Starts Breaking Down

Enum + Lambda is intentionally lightweight.

As strategies become more complex, it begins to show its limits.

For example, suppose a payment strategy needs:

  • a payment gateway
  • audit logging
  • retry handling
  • metrics
  • fraud detection

Trying to squeeze all of that into an enum quickly becomes difficult to read and maintain.

At that point, each strategy deserves its own class.


Enum + Lambda vs Factory + Strategy

Use Enum + Lambda when the behavior is simple.

CARD
CASH
WALLET

Each strategy is only a few lines of code.

Use Factory + Strategy classes when each strategy becomes a feature of its own.

CardPaymentStrategy
CashPaymentStrategy
WalletPaymentStrategy
CryptoPaymentStrategy

These implementations often have their own dependencies, configuration, unit tests, and collaborators.

A practical rule is:

Start with Enum + Lambda. When strategies grow beyond simple business rules, refactor to Factory + Strategy classes.

This keeps the design simple for today's requirements while leaving room for future growth.


A Common Mistake

One mistake is allowing lambda expressions to become large.

Avoid this:

CARD(amount -> {
    // 40 lines of business logic
})

Instead, delegate the work to a private method.

CARD(PaymentMethod::calculateCardFee)

This keeps the enum compact while preserving readability.


Summary

Enum + Lambda offers a simple way to implement the Strategy Pattern by embedding behavior directly in enum constants. It works best for small, fixed rule sets where simplicity matters more than flexibility. For more complex or dependency-heavy strategies, a traditional Factory + Strategy approach is more suitable. The key is choosing the simplest design that remains clear and maintainable.

Did you find this article useful?