📘 Premium Read: Access my best content on Medium member-only articles — deep dives into Java, Spring Boot, Microservices, backend architecture, interview preparation, career advice, and industry-standard best practices.
🎓 Top 15 Udemy Courses (80-90% Discount): My Udemy Courses - Ramesh Fadatare — All my Udemy courses are real-time and project oriented courses.
▶️ Subscribe to My YouTube Channel (176K+ subscribers): Java Guides on YouTube
▶️ For AI, ChatGPT, Web, Tech, and Generative AI, subscribe to another channel: Ramesh Fadatare on YouTube
In backend development, we often face scenarios where we need to execute different logic based on a type or condition — for example:
- Different discount calculations based on customer type
- Multiple payment methods
- Various file parsers depending on the file format
- Email vs. SMS vs. Push for notifications
You could easily go for if-else
or switch
statements to handle such logic. But as your project grows, that approach becomes hard to maintain and test.
That’s where the Strategy Pattern comes in.
In this article, we’ll cover:
- What the Strategy Pattern is
- When to use it in Spring Boot projects
- A complete real-world example: calculating discounts based on customer type
- Clean code with map-based Spring injection
- Testing and extension tips
Let’s break it down, step-by-step.
What is the Strategy Pattern?
The Strategy Pattern is a behavioral design pattern that allows you to define a family of algorithms, encapsulate each one, and make them interchangeable at runtime.
Instead of hardcoding logic in
if-else
, you create a separate class for each strategy and choose the right one dynamically.
It’s especially helpful when you have different ways of doing the same kind of task — like calculating discounts.
Real-World Example: Dynamic Discount Strategy
Use Case
You're building an e-commerce platform where different types of customers get different discounts:
- Regular: no discount
- Premium: 10% discount
- VIP: 20% discount
Your service should calculate the final price based on the customer type — and you want to avoid writing if-else
blocks every time.
We’ll solve this using the Strategy Pattern with Spring Boot.
✅ Step 1: Define the Strategy Interface
public interface DiscountStrategy {
double applyDiscount(double originalPrice);
String getCustomerType(); // Used to map the strategy
}
All discount types will implement this interface.
✅ Step 2: Implement Strategies
Regular Customer – no discount
import org.springframework.stereotype.Component;
@Component
public class RegularDiscountStrategy implements DiscountStrategy {
@Override
public double applyDiscount(double originalPrice) {
return originalPrice;
}
@Override
public String getCustomerType() {
return "REGULAR";
}
}
Premium Customer – 10% off
import org.springframework.stereotype.Component;
@Component
public class PremiumDiscountStrategy implements DiscountStrategy {
@Override
public double applyDiscount(double originalPrice) {
return originalPrice * 0.90;
}
@Override
public String getCustomerType() {
return "PREMIUM";
}
}
VIP Customer – 20% off
import org.springframework.stereotype.Component;
@Component
public class VipDiscountStrategy implements DiscountStrategy {
@Override
public double applyDiscount(double originalPrice) {
return originalPrice * 0.80;
}
@Override
public String getCustomerType() {
return "VIP";
}
}
Each class contains its own logic, and you can add more strategies easily.
✅ Step 3: Create the Strategy Context
This class selects the right strategy based on the customer type.
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Component
public class DiscountStrategyContext {
private final Map<String, DiscountStrategy> strategyMap = new HashMap<>();
public DiscountStrategyContext(List<DiscountStrategy> strategies) {
for (DiscountStrategy strategy : strategies) {
strategyMap.put(strategy.getCustomerType(), strategy);
}
}
public DiscountStrategy getStrategy(String customerType) {
DiscountStrategy strategy = strategyMap.get(customerType.toUpperCase());
if (strategy == null) {
throw new IllegalArgumentException("Unknown customer type: " + customerType);
}
return strategy;
}
}
With Spring's help, all strategy beans are auto-injected and stored in a map.
✅ Step 4: Create the Discount Service
import org.springframework.stereotype.Service;
@Service
public class DiscountService {
private final DiscountStrategyContext strategyContext;
public DiscountService(DiscountStrategyContext strategyContext) {
this.strategyContext = strategyContext;
}
public double calculateFinalPrice(String customerType, double originalPrice) {
DiscountStrategy strategy = strategyContext.getStrategy(customerType);
return strategy.applyDiscount(originalPrice);
}
}
This service doesn’t care about how discounts are calculated — it just picks the correct strategy and delegates the call.
✅ Step 5: Create the REST Controller
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/discounts")
public class DiscountController {
private final DiscountService discountService;
public DiscountController(DiscountService discountService) {
this.discountService = discountService;
}
@GetMapping
public ResponseEntity<String> getDiscountedPrice(
@RequestParam String customerType,
@RequestParam double price) {
double finalPrice = discountService.calculateFinalPrice(customerType, price);
return ResponseEntity.ok("Final price for " + customerType + ": $" + finalPrice);
}
}
🧪 Test It
Request
GET /api/discounts?customerType=VIP&price=100
Output
Final price for VIP: $80.0
Change customerType to REGULAR
or PREMIUM
to see different outputs.
✅ No if-else
, just clean strategy selection.
Why Use Strategy Pattern in Spring Boot?
Problem | Strategy Pattern Fix |
---|---|
Growing if-else blocks |
Each logic goes in its own class |
Hard to test | Each strategy is independently testable |
Tight coupling | Logic is decoupled from service or controller |
Hard to extend | Just add a new strategy class and Spring picks it up |
🔁 More Use Cases for Strategy Pattern
Use Case | Description |
---|---|
Payment method routing | PayPal, Stripe, UPI, etc. |
Tax calculation | Based on region or category |
Notification sending | Email, SMS, push |
Shipping fee calculation | By weight, speed, or carrier |
Report formatting | PDF, Excel, HTML |
✅ Testing Strategy Logic
You can test strategies like this:
@SpringBootTest
public class VipDiscountStrategyTest {
@Autowired
private VipDiscountStrategy strategy;
@Test
public void shouldApply20PercentDiscount() {
double result = strategy.applyDiscount(100.0);
Assertions.assertEquals(80.0, result);
}
}
Testing the context:
@SpringBootTest
public class DiscountStrategyContextTest {
@Autowired
private DiscountStrategyContext context;
@Test
public void shouldReturnPremiumStrategy() {
DiscountStrategy strategy = context.getStrategy("PREMIUM");
Assertions.assertTrue(strategy instanceof PremiumDiscountStrategy);
}
}
✅ Best Practices
- Use meaningful strategy names – match with request inputs
- Use
@Component
on each strategy – for auto-injection - Avoid putting logic in the context – let strategies do the work
- Validate inputs in each strategy if needed
- Write separate tests per strategy – keeps bugs isolated
⚠️ When Not to Use Strategy Pattern
Avoid the Strategy Pattern if:
- You have only one or two simple conditions
- The logic won’t change or grow
- You prefer simple lambdas (for very short logic)
Remember: patterns are tools, not rules. Use them when they help — not when they complicate things.
Summary
Step | What We Did |
---|---|
1 | Defined a common DiscountStrategy interface |
2 | Created separate strategy classes for each customer type |
3 | Built a strategy context to manage strategy selection |
4 | Delegated logic from service to selected strategy |
5 | Created a clean controller and tested the full flow |
This structure keeps your codebase clean, easy to test, and ready for future growth.
Final Thoughts
The Strategy Pattern is one of the most useful and practical patterns you can apply in a Spring Boot project.
It helps you replace messy conditionals with clean, modular classes — each responsible for a single job. With Spring’s dependency injection, implementing this pattern becomes even simpler.
Start using Strategy Pattern the next time you see multiple if-else
branches for business logic. Your future self (and your team) will thank you.
Clean code is simple code. Strategy Pattern helps you get there — one use case at a time.
Comments
Post a Comment
Leave Comment