Fundamentals 20 min read

Master 7 Essential Java Design Patterns with Real-World Spring Boot Examples

This article introduces seven core design patterns—Singleton, Factory, Builder, Strategy, Observer, Proxy, and Template—explaining their purpose, typical use cases, and providing complete Java code examples that integrate with Spring Boot 3 and Java 21, helping developers write cleaner, more maintainable software.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Master 7 Essential Java Design Patterns with Real-World Spring Boot Examples

Design patterns offer a set of proven, reusable solutions for recurring software design problems, improving code reusability, readability, and extensibility. This article presents the seven most commonly used patterns in Java development, each accompanied by clear explanations and complete code examples that can be run with Java 21 and Spring Boot 3.

1. Singleton Pattern

The Singleton pattern ensures a class has only one instance, often used for shared resources such as configuration, cache, or thread pools.

<code>public class Singleton {
  private static volatile Singleton instance;
  private Singleton() {}
  public static Singleton getInstance() {
    if (instance == null) {
      synchronized (Singleton.class) {
        if (instance == null) {
          instance = new Singleton();
        }
      }
    }
    return instance;
  }
}
</code>

Using volatile guarantees visibility and prevents instruction reordering during instance creation. An alternative, thread‑safe approach recommended by *Effective Java* is the enum singleton:

<code>public enum SingletonEnum {
  INSTANCE;
  public void operator() {
    System.out.println("TODO PACK");
  }
}
</code>

2. Factory Pattern

The Factory pattern abstracts object creation behind a factory class, allowing client code to request objects without knowing their concrete classes.

<code>public class FactoryPatternExample {
  static abstract class Vehicle {
    public abstract void drive();
  }
  static class Car extends Vehicle {
    @Override public void drive() { System.out.println("Driving a car."); }
  }
  static class Bicycle extends Vehicle {
    @Override public void drive() { System.out.println("Riding a bicycle."); }
  }
  static class Truck extends Vehicle {
    @Override public void drive() { System.out.println("Driving a truck."); }
  }
  class VehicleFactory {
    public static Vehicle createVehicle(String type) {
      return switch (type) {
        case "car" -> new Car();
        case "bicycle" -> new Bicycle();
        case "truck" -> new Truck();
        default -> throw new IllegalArgumentException("Invalid type: " + type);
      };
    }
  }
  public static void main(String[] args) {
    Vehicle car = VehicleFactory.createVehicle("car");
    car.drive();
    Vehicle bike = VehicleFactory.createVehicle("bicycle");
    bike.drive();
    Vehicle truck = VehicleFactory.createVehicle("truck");
    truck.drive();
  }
}
</code>

Spring’s BeanFactory and ApplicationContext are practical examples of the Factory pattern.

3. Builder Pattern

The Builder pattern separates the construction of a complex object from its representation, allowing step‑by‑step configuration.

<code>class Computer {
  private String cpu;
  private String memory;
  private String storage;
  private String graphicsCard;
  private String operatingSystem;
  private Computer(Builder builder) {
    this.cpu = builder.cpu;
    this.memory = builder.memory;
    this.storage = builder.storage;
    this.graphicsCard = builder.graphicsCard;
    this.operatingSystem = builder.operatingSystem;
  }
  public static class Builder {
    private String cpu;
    private String memory;
    private String storage;
    private String graphicsCard;
    private String operatingSystem;
    public Builder(String cpu, String memory) { this.cpu = cpu; this.memory = memory; }
    public Builder storage(String storage) { this.storage = storage; return this; }
    public Builder graphicsCard(String graphicsCard) { this.graphicsCard = graphicsCard; return this; }
    public Builder operatingSystem(String os) { this.operatingSystem = os; return this; }
    public Computer build() { return new Computer(this); }
  }
  public void showConfiguration() {
    System.out.println("CPU: " + cpu);
    System.out.println("Memory: " + memory);
    System.out.println("Storage: " + storage);
    System.out.println("Graphics Card: " + graphicsCard);
    System.out.println("OS: " + operatingSystem);
  }
}
public class BuilderPatternExample {
  public static void main(String[] args) {
    Computer pc = new Computer.Builder("Intel i7", "16GB")
        .storage("1TB SSD")
        .graphicsCard("NVIDIA RTX 4060")
        .operatingSystem("Windows 10")
        .build();
    pc.showConfiguration();
  }
}
</code>

4. Strategy Pattern

Strategy defines a family of algorithms, encapsulates each one, and makes them interchangeable.

<code>interface TravelStrategy {
  void travel();
  double calculateCost(double distance);
  double calculateTime(double distance);
}
class WalkStrategy implements TravelStrategy {
  public void travel() { System.out.println("步行."); }
  public double calculateCost(double d) { return 0.0; }
  public double calculateTime(double d) { return d / 5.0; }
}
class BikeStrategy implements TravelStrategy {
  public void travel() { System.out.println("自行车🚲."); }
  public double calculateCost(double d) { return 0.0; }
  public double calculateTime(double d) { return d / 15.0; }
}
class CarStrategy implements TravelStrategy {
  public void travel() { System.out.println("汽车🚕."); }
  public double calculateCost(double d) { return d * 0.5; }
  public double calculateTime(double d) { return d / 60.0; }
}
class AirplaneStrategy implements TravelStrategy {
  public void travel() { System.out.println("飞机✈."); }
  public double calculateCost(double d) { return d * 0.8; }
  public double calculateTime(double d) { return d / 800.0; }
}
class TravelPlanner {
  private TravelStrategy strategy;
  public TravelPlanner(TravelStrategy s) { this.strategy = s; }
  public void setStrategy(TravelStrategy s) { this.strategy = s; }
  public void planTravel() { strategy.travel(); }
  public double calculateCost(double d) { return strategy.calculateCost(d); }
  public double calculateTime(double d) { return strategy.calculateTime(d); }
}
public class StrategyPatternExample {
  public static void main(String[] args) {
    double distance = 100.0;
    TravelPlanner planner = new TravelPlanner(new WalkStrategy());
    planner.planTravel();
    System.out.println("Cost: " + planner.calculateCost(distance));
    System.out.println("Time: " + planner.calculateTime(distance));
    // Switch to other strategies similarly
  }
}
</code>

5. Observer Pattern

Observer establishes a one‑to‑many dependency so that when the subject changes state, all observers are notified.

<code>interface StockObserver { void update(double price); }
class Investor implements StockObserver {
  private String name;
  public Investor(String name) { this.name = name; }
  public void update(double price) {
    System.out.println(name + " 价格发生变化,当前价格: " + price);
  }
}
interface StockSubject {
  void registerObserver(StockObserver o);
  void removeObserver(StockObserver o);
  void notifyObservers();
}
class Stock implements StockSubject {
  private List<StockObserver> observers = new ArrayList<>();
  private double price;
  public void registerObserver(StockObserver o) { observers.add(o); }
  public void removeObserver(StockObserver o) { observers.remove(o); }
  public void setPrice(double p) { this.price = p; notifyObservers(); }
  public void notifyObservers() { for (StockObserver o : observers) o.update(price); }
}
public class ObserverPatternExample {
  public static void main(String[] args) {
    Stock stock = new Stock();
    stock.registerObserver(new Investor("Pack"));
    stock.registerObserver(new Investor("XO"));
    stock.registerObserver(new Investor("Pxg"));
    stock.setPrice(100.0);
    stock.setPrice(105.0);
    stock.removeObserver(new Investor("XO")); // example removal
    stock.setPrice(110.0);
  }
}
</code>

6. Proxy Pattern

Proxy provides a surrogate or placeholder for another object to control access to it.

<code>interface UserService { void save(); }
class UserServiceImpl implements UserService {
  public void save() { System.out.println("UserService save method invoke..."); }
}
class UserServiceInvocationHandler implements InvocationHandler {
  private Object target;
  public UserServiceInvocationHandler(Object target) { this.target = target; }
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    System.err.println("execute before...");
    Object result = method.invoke(target, args);
    System.err.println("execute after...");
    return result;
  }
}
public class DynamicProxyExample {
  public static void main(String[] args) {
    UserService real = new UserServiceImpl();
    UserServiceInvocationHandler handler = new UserServiceInvocationHandler(real);
    UserService proxy = (UserService) Proxy.newProxyInstance(
        UserService.class.getClassLoader(),
        new Class<?>[] { UserService.class }, handler);
    proxy.save();
  }
}
</code>

7. Template Method Pattern

Template defines the skeleton of an algorithm in a method, deferring some steps to subclasses.

<code>abstract class BeverageMaker {
  public final void makeBeverage() {
    prepareIngredients();
    brew();
    pourInCup();
    addCondiments();
  }
  protected abstract void prepareIngredients();
  protected abstract void brew();
  protected void pourInCup() { System.out.println("饮品倒入杯中"); }
  protected abstract void addCondiments();
}
class CoffeeMaker extends BeverageMaker {
  protected void prepareIngredients() { System.out.println("准备咖啡豆及水"); }
  protected void brew() { System.out.println("煮咖啡"); }
  protected void addCondiments() { System.out.println("添加糖和牛奶"); }
}
class TeaMaker extends BeverageMaker {
  protected void prepareIngredients() { System.out.println("准备茶叶和水"); }
  protected void brew() { System.out.println("使用沸水泡茶"); }
  protected void addCondiments() { System.out.println("添加柠檬🍋"); }
}
public class TemplateMethodDemo {
  public static void main(String[] args) {
    BeverageMaker coffee = new CoffeeMaker();
    System.out.println("制作咖啡:");
    coffee.makeBeverage();
    System.out.println("-----------------");
    BeverageMaker tea = new TeaMaker();
    System.out.println("制作茶:");
    tea.makeBeverage();
  }
}
</code>

These patterns are widely used in JDK (e.g., java.util.Collections , java.util.concurrent ) and Spring (e.g., BeanFactory , ApplicationContext , JdbcTemplate , RestTemplate ), demonstrating their practical relevance in modern backend development.

design patternsJavaSpring BootsingletonStrategyFactoryObserverBuilder
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.