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

Enum with Lambda (Strategy Pattern) in Java
Enum + Lambda is one of the cleanest ways to implement Strategy Pattern in Java when the number of strategies is small to medium and known in advance.
It helps remove long if-else blocks, keeps behavior close to the type, and makes code easier to read and extend.
Why this pattern is useful
In many projects we write code like this:
if (method == PaymentMethod.CARD) {
// logic 1
} else if (method == PaymentMethod.CASH) {
// logic 2
} else if (method == PaymentMethod.DIGITAL_WALLET) {
// logic 3
}This becomes hard to maintain as the system grows.
With Enum + Lambda, each strategy is defined once and used directly.
Core idea
enumrepresents the available strategies- each enum constant stores a lambda function
- client code calls one method and executes the selected strategy
You still get Strategy Pattern benefits, but with less boilerplate than separate classes.
Quick decision: Enum + Lambda vs Factory + Strategy classes
If you read both guides, use this practical rule:
- Choose Enum + Lambda when strategies are finite, simple, and mostly stateless.
- Choose Factory + Strategy classes when each strategy has richer logic, dependencies, or separate test setup.
| Situation | Better choice |
|---|---|
| 3-8 fixed business rules (fees, discounts, routing) | Enum + Lambda |
| Each strategy calls multiple services (gateway, audit, retry, metrics) | Factory + Strategy classes |
| You want very low boilerplate and fast readability | Enum + Lambda |
| You need dependency injection per strategy | Factory + Strategy classes |
| Strategies may be added dynamically via config/plugin | Factory + Strategy classes |
Think of Enum + Lambda as a lightweight strategy model. Once behavior becomes large, move to class-based strategies.
Small class diagram
The diagram below shows how enum constants hold behavior and client code invokes one strategy through the enum.
Basic implementation (Payment fee calculation)
Imagine a checkout flow where each payment method has a different processing fee.
import java.math.BigDecimal;
import java.math.RoundingMode;
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 fee(BigDecimal amount) {
return feeCalculator.apply(amount).setScale(2, RoundingMode.HALF_UP);
}
}Usage:
import java.math.BigDecimal;
public class CheckoutService {
public BigDecimal totalPayable(BigDecimal amount, PaymentMethod method) {
BigDecimal fee = method.fee(amount);
return amount.add(fee);
}
public static void main(String[] args) {
CheckoutService service = new CheckoutService();
BigDecimal amount = new BigDecimal("1000.00");
System.out.println(service.totalPayable(amount, PaymentMethod.CARD));
System.out.println(service.totalPayable(amount, PaymentMethod.CASH));
System.out.println(service.totalPayable(amount, PaymentMethod.DIGITAL_WALLET));
}
}This is compact, readable, and avoids scattering strategy logic in multiple files.
Real-world example 1: Notification routing
In many systems you send alerts through multiple channels.
import java.util.function.BiConsumer;
public enum NotificationChannel {
EMAIL(NotificationChannel::sendEmail),
SMS(NotificationChannel::sendSms),
PUSH(NotificationChannel::sendPush);
private final BiConsumer<String, String> sender;
NotificationChannel(BiConsumer<String, String> sender) {
this.sender = sender;
}
public void send(String user, String message) {
sender.accept(user, message);
}
private static void sendEmail(String user, String message) {
String subject = "Action Required";
// Real systems call an EmailClient here.
System.out.println("EMAIL => user=" + user + ", subject=" + subject + ", body=" + message);
}
private static void sendSms(String user, String message) {
String compact = message.length() > 140 ? message.substring(0, 140) : message;
// Real systems call an SmsGateway here.
System.out.println("SMS => user=" + user + ", text=" + compact);
}
private static void sendPush(String user, String message) {
String title = "Notification";
// Real systems call PushProvider/APNs/FCM here.
System.out.println("PUSH => user=" + user + ", title=" + title + ", body=" + message);
}
}Usage:
public class NotificationService {
public void notify(String user, String message, NotificationChannel channel) {
channel.send(user, message);
}
}Why this works well:
- no channel-specific
if-else - each channel behavior is centralized
- adding a new channel is one enum constant
Real-world example 2: Discount policy engine
E-commerce promotions are often strategy driven.
import java.math.BigDecimal;
import java.util.function.UnaryOperator;
public enum DiscountPolicy {
NO_DISCOUNT(amount -> amount),
FESTIVAL_10(amount -> amount.multiply(BigDecimal.valueOf(0.90))),
PREMIUM_15(amount -> amount.multiply(BigDecimal.valueOf(0.85)));
private final UnaryOperator<BigDecimal> applyDiscount;
DiscountPolicy(UnaryOperator<BigDecimal> applyDiscount) {
this.applyDiscount = applyDiscount;
}
public BigDecimal apply(BigDecimal amount) {
return applyDiscount.apply(amount);
}
}Service:
import java.math.BigDecimal;
public class PricingService {
public BigDecimal finalPrice(BigDecimal basePrice, DiscountPolicy policy) {
return policy.apply(basePrice);
}
}This pattern is clean for product pricing, shipping options, tax rules, or commission rules.
When Enum + Lambda is a great choice
Use it when:
- strategy set is finite and stable
- each strategy is simple to medium complexity
- you want to reduce class explosion
- you need fast readability for the team
When not to use it
Prefer classic Strategy classes when:
- strategy logic is large or needs multiple helper dependencies
- each strategy has complex state
- strategies must be loaded dynamically (plugins/config/db)
- teams prefer one-class-per-behavior for test isolation
A good practical rule:
- simple behavior -> enum + lambda
- complex behavior -> interface + classes
Common mistakes and fixes
Mistake 1: Putting too much code inside lambdas
If lambda blocks become large, move logic to private static methods.
import java.math.BigDecimal;
import java.util.function.Function;
public enum PaymentMethod {
CARD(PaymentMethod::cardFee),
CASH(PaymentMethod::cashFee),
DIGITAL_WALLET(PaymentMethod::walletFee);
private final Function<BigDecimal, BigDecimal> feeCalculator;
PaymentMethod(Function<BigDecimal, BigDecimal> feeCalculator) {
this.feeCalculator = feeCalculator;
}
private static BigDecimal cardFee(BigDecimal amount) {
return amount.multiply(BigDecimal.valueOf(0.02));
}
private static BigDecimal cashFee(BigDecimal amount) {
return BigDecimal.ZERO;
}
private static BigDecimal walletFee(BigDecimal amount) {
return amount.multiply(BigDecimal.valueOf(0.01));
}
}Mistake 2: Using double for money
Always use BigDecimal for currency.
Mistake 3: Missing default behavior in API input mapping
When mapping external text values to enum, handle invalid values safely.
public static PaymentMethod from(String value) {
for (PaymentMethod method : values()) {
if (method.name().equalsIgnoreCase(value)) {
return method;
}
}
throw new IllegalArgumentException("Unsupported payment method: " + value);
}Testing approach
Enum strategy testing is straightforward.
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.math.BigDecimal;
import org.junit.jupiter.api.Test;
class PaymentMethodTest {
@Test
void cardFeeShouldBeTwoPercent() {
BigDecimal fee = PaymentMethod.CARD.fee(new BigDecimal("1000.00"));
assertEquals(new BigDecimal("20.00"), fee);
}
@Test
void cashFeeShouldBeZero() {
BigDecimal fee = PaymentMethod.CASH.fee(new BigDecimal("1000.00"));
assertEquals(new BigDecimal("0.00"), fee);
}
}Comparison: classic Strategy vs Enum + Lambda
| Aspect | Classic Strategy (classes) | Enum + Lambda |
|---|---|---|
| Boilerplate | Higher | Lower |
| Scalability for complex logic | Better | Limited |
| Readability for small use cases | Medium | High |
| Dynamic runtime loading | Better | Not ideal |
| Dependency injection friendliness | Better | Limited |
Final takeaway
Enum + Lambda is a very practical Strategy Pattern variant for Java teams.
It is especially useful in real-world modules such as:
- payment and fee rules
- pricing and discounts
- notification routing
- tax and commission policies
Start with Enum + Lambda for simple, known strategies. If behavior grows significantly, refactor to classic Strategy classes. That gives you both speed and long-term maintainability.
Enjoyed this article?
Check out more articles or share this with your network.