C
📦 Part 1 of 4 — Creational

GoF Creational
Design Patterns

5 creational patterns with production Java examples, Spring internals, thread-safety deep dives, and senior-level interview Q&A. These patterns answer HOW objects get created and who controls that creation.

Singleton + volatile DCL Factory + Spring @Service Abstract Factory + @Profile Builder + Lombok internals Prototype + deep copy HikariCP pool Spring FactoryBean<T> CGLIB @Configuration Cloneable pitfalls
5
Creational Patterns
20+
Code Snippets
15+
Interview Q&As
1/4
GoF Series
Day 1 · W1 Singleton Pattern Creational
Intent

Ensure a class has only one instance throughout JVM lifetime and provide a global access point to it. Created lazily (on first use) or eagerly (at class load time).

Singleton solves shared expensive resources — database connection pools, logger factories, application config — that must be created once and reused everywhere. The real challenge is thread-safety during initialization.

Why it's controversial: A Singleton is a global variable dressed in a class. It creates hidden coupling, makes unit testing hard, violates Single Responsibility. Modern answer: let Spring manage singletons — every @Bean is singleton by default. Only write Singleton manually when you have no DI container.

Java6 Implementations — Thread Safety Compared
// 1. EAGER — JVM class loading is thread-safe by spec.
//    Simple. Instance created even if never used (startup waste).
public class EagerSingleton {
    private static final EagerSingleton INSTANCE = new EagerSingleton();
    private EagerSingleton() {}
    public static EagerSingleton getInstance() { return INSTANCE; }
}

// 2. BROKEN LAZY — NEVER USE.
//    Race: two threads both see null, both call new → TWO instances.
public class BrokenLazy {
    private static BrokenLazy instance;
    public static BrokenLazy getInstance() {
        if (instance == null) instance = new BrokenLazy(); // ❌ race condition
        return instance;
    }
}

// 3. SYNCHRONIZED METHOD — thread-safe but every call acquires the lock.
//    Lock overhead: ~10-100 ns/call. Avoid for high-traffic paths.
public class SyncSingleton {
    private static SyncSingleton instance;
    public static synchronized SyncSingleton getInstance() {
        if (instance == null) instance = new SyncSingleton(); // ✅ safe, ⚠️ slow
        return instance;
    }
}

// 4. DOUBLE-CHECKED LOCKING (DCL) — production standard outside Spring.
//    Lock ONLY on first creation. Zero lock overhead for 99.99% of calls.
//
//    WHY volatile? new MyClass() = 3 JVM ops:
//      (a) allocate memory
//      (b) initialize fields
//      (c) assign reference to INSTANCE
//    Without volatile, CPU can reorder: (a)→(c)→(b).
//    Thread B sees non-null INSTANCE but object is uninitialized → NPE / corruption!
//    volatile establishes happens-before: all writes complete before ref is visible.
public class DatabaseConnectionManager {
    private static volatile DatabaseConnectionManager INSTANCE; // volatile = critical
    private final HikariDataSource dataSource;

    private DatabaseConnectionManager() {
        HikariConfig cfg = new HikariConfig();
        cfg.setJdbcUrl("jdbc:postgresql://localhost:5432/orders");
        cfg.setMaximumPoolSize(20);
        cfg.setMinimumIdle(5);
        cfg.setConnectionTimeout(30_000);
        this.dataSource = new HikariDataSource(cfg);
    }

    public static DatabaseConnectionManager getInstance() {
        if (INSTANCE == null) {              // 1st check — no lock (fast path)
            synchronized (DatabaseConnectionManager.class) {
                if (INSTANCE == null) {    // 2nd check — inside lock
                    INSTANCE = new DatabaseConnectionManager();
                }
            }
        }
        return INSTANCE;
    }
    public Connection getConnection() throws SQLException {
        return dataSource.getConnection();
    }
}

// 5. ENUM SINGLETON — Josh Bloch's recommendation (Effective Java §3).
//    JVM guarantees one instance per enum constant.
//    Free serialization safety. Cannot extend another class.
public enum AppConfig {
    INSTANCE;
    private final Properties props = loadProps();
    public String get(String key)           { return props.getProperty(key); }
    public String get(String key, String def) { return props.getProperty(key, def); }
    private Properties loadProps() { /* load from classpath */ return new Properties(); }
}
// Usage: AppConfig.INSTANCE.get("db.url")

// 6. BILL PUGH HOLDER — cleanest lazy singleton. Zero sync overhead.
//    Inner class not loaded until getInstance() called first time.
//    JVM class loading is thread-safe by spec.
public class ReportEngine {
    private ReportEngine() {}
    private static class Holder {
        // Loaded only when Holder is accessed → thread-safe, lazy, no lock
        static final ReportEngine INSTANCE = new ReportEngine();
    }
    public static ReportEngine getInstance() { return Holder.INSTANCE; }
}
JavaSpring Singleton — The Right Way (Never Write It Manually)
// Spring's ApplicationContext IS the Singleton registry.
// Every @Bean is singleton-scoped by default.
// @Configuration classes are wrapped in CGLIB proxy to intercept @Bean calls.
@Configuration
public class InfraConfig {

    @Bean
    public DataSource dataSource() {   // Spring creates EXACTLY ONE instance
        HikariConfig cfg = new HikariConfig();
        cfg.setJdbcUrl("${spring.datasource.url}");
        cfg.setMaximumPoolSize(20);
        return new HikariDataSource(cfg);
    }

    @Bean
    public OrderRepository orderRepository() {
        return new OrderJdbcRepo(dataSource()); // CGLIB intercepts → returns cached bean
    }

    @Bean
    public ReportService reportService() {
        return new ReportService(dataSource()); // SAME DataSource instance ✅
    }
    // orderRepository.ds == reportService.ds → TRUE (singleton)
}

// Prototype scope: new instance per injection point
@Bean
@Scope("prototype")
public RequestContext requestContext() { return new RequestContext(); }
ImplementationThread-Safe?Lazy?PerformanceWhen to use
Eager✅ JVMFastLightweight or always-needed instances
Lazy (broken)❌ RaceFastNEVER — race condition
Synchronized methodSlow (lock every call)Only very low traffic code
Double-Checked Locking✅ (volatile!)Fast after initProduction standard outside Spring
Enum✅ JVMFastStateless config holders
Bill Pugh Holder✅ JVMFastestBest lazy singleton — most elegant
🎯 Interview Questions
How does Spring @Bean achieve singleton? Can two threads create duplicate Spring beans?
Spring CGLIB proxy: Spring wraps @Configuration classes with a CGLIB subclass. When a @Bean method is called from another @Bean method, CGLIB intercepts and checks DefaultSingletonBeanRegistry — if the bean exists, returns the cached instance. Thread-safety during initialization is handled by synchronized blocks inside getSingleton() — essentially double-checked locking. After context startup, beans are fully initialized; thread-safety then depends on the bean itself being stateless (it should be).

Why is volatile required in DCL? What breaks without it?
The reordering bug: new Foo() = 3 JVM ops: allocate, init fields, assign reference. Without volatile, CPU can reorder: allocate → assign → init. Thread B sees a non-null reference (assignment happened) but the object is uninitialized (init hasn't run) → NPE or corrupted state on any field access. volatile establishes a happens-before: all writes must complete before the reference assignment is visible to other threads.

When should you NOT use Singleton?
Avoid when: State varies per thread (use ThreadLocal/prototype scope). Unit test isolation needed (singletons with hard deps are untestable without reflection hacks). The resource doesn't actually need sharing. Production pitfalls: Hidden global state, initialization order issues, memory leaks if singleton holds references to short-lived objects (classloader leaks in app servers). Senior answer: Prefer Spring-managed singletons — testable via mocking, proper lifecycle (@PostConstruct/@PreDestroy), scope changes without code changes.
Day 2 · W1 Factory Method Pattern Creational
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.
Day 3 · W1 Abstract Factory Pattern Creational
Intent

Create families of related objects without specifying concrete classes. A factory-of-factories. Each concrete factory creates a set of products that are designed to work together — switching factories switches the entire product family atomically.

Key difference from Factory Method: Factory Method creates ONE product type. Abstract Factory creates MULTIPLE related products as a group (a "kit"). Change the factory = all products change together, guaranteed compatible.

Problem it solves: 3 channels × 3 environments = 9 combinations. Without Abstract Factory: 9 classes + conditional wiring + risk of mixing production email with test SMS. With Abstract Factory: 3 environment factories + 3 channel interfaces = 6 classes, zero risk of mixing.

JavaNotification System — Abstract Factory + Spring @Profile
// Product Interfaces — the "family" members
public interface EmailSender { CompletableFuture<Void> send(String to, String subject, String html); }
public interface SmsSender   { CompletableFuture<Void> send(String phone, String msg); }
public interface PushSender  { CompletableFuture<Void> send(String token, String title, String body); }

// Abstract Factory Interface
public interface NotificationFactory {
    EmailSender createEmailSender();
    SmsSender   createSmsSender();
    PushSender  createPushSender();
}

// Concrete Factory 1: Production — real external services
@Configuration
@Profile("production")
public class ProductionNotificationFactory implements NotificationFactory {
    @Bean @Override
    public EmailSender createEmailSender()  { return new AwsSesEmailSender("us-east-1"); }
    @Bean @Override
    public SmsSender   createSmsSender()    { return new TwilioSmsSender(sid, token); }
    @Bean @Override
    public PushSender  createPushSender()   { return new FirebasePushSender("creds.json"); }
}

// Concrete Factory 2: Test — all stubs, no real calls, no side effects
@Configuration
@Profile({"test", "local"})
public class TestNotificationFactory implements NotificationFactory {
    @Bean @Override
    public EmailSender createEmailSender() {
        return (to, subj, html) -> {
            log.info("[STUB EMAIL] to={} subject={}", to, subj);
            return CompletableFuture.completedFuture(null);
        };
    }
    @Bean @Override
    public SmsSender  createSmsSender() {
        return (phone, msg) -> {
            log.info("[STUB SMS] phone={}", phone);
            return CompletableFuture.completedFuture(null);
        };
    }
    @Bean @Override
    public PushSender createPushSender() {
        return (token, t, b) -> {
            log.info("[STUB PUSH] token={}", token);
            return CompletableFuture.completedFuture(null);
        };
    }
}

// Client: only knows interfaces — zero knowledge of which factory is active
@Service @RequiredArgsConstructor
public class OrderNotificationService {
    private final EmailSender emailSender;  // prod: SES | test: stub
    private final SmsSender   smsSender;    // prod: Twilio | test: stub
    private final PushSender  pushSender;   // prod: FCM | test: stub

    public void notifyOrderPlaced(Order order) {
        CompletableFuture.allOf(
            emailSender.send(order.getEmail(), "Order Confirmed", buildEmailHtml(order)),
            smsSender.send(order.getPhone(), "Your order is confirmed! Track at myapp.com"),
            pushSender.send(order.getDeviceToken(), "Order Placed!", buildPushBody(order))
        ).exceptionally(e -> { log.warn("Notification failed", e); return null; });
    }
    // In production: real SES+Twilio+FCM. In test: all stubs. Service code IDENTICAL.
}
🎯 Interview Questions
Abstract Factory vs Factory Method — what's the concrete difference?
Factory Method: ONE abstract creation method, ONE product type. getProcessor(PaymentMethod) returns one PaymentProcessor.
Abstract Factory: ONE factory object, MULTIPLE product types as a family. NotificationFactory creates EmailSender + SmsSender + PushSender — all guaranteed compatible.
Rule: Factory Method = varying ONE product type. Abstract Factory = varying a FAMILY of 2+ products that must always be consistent with each other.

How does Spring @Profile implement Abstract Factory?
Each @Configuration @Profile("production") class is a concrete Abstract Factory. Spring activates one profile at startup — selecting which factory class loads and which family of beans gets created. Client services depend only on product interfaces (EmailSender, SmsSender), never on the factory. Switching environments = changing spring.profiles.active. Zero client code changes. This is why Abstract Factory is one of Spring's most natural patterns.
Day 4 · W1 Builder Pattern Creational
Intent

Construct complex objects step by step. Eliminate the "telescoping constructor" anti-pattern where a class needs dozens of constructor overloads for different combinations of optional parameters.

❌ Telescoping Anti-Pattern
new Order(
  customerId, items, addr,
  PaymentMethod.UPI,
  null, null, null,    // what is null #5?
  false, null, "SAVE20",
  null, NORMAL, null   // unreadable!
);
✅ Builder Pattern
Order.builder()
  .customerId(id)
  .items(items)
  .shippingAddress(addr)
  .paymentMethod(UPI)
  .couponCode("SAVE20")
  .priority(NORMAL)
  .build(); // validated
JavaManual Builder + Lombok @Builder + Spring ResponseEntity / WebClient
// MANUAL BUILDER — shows exactly what Lombok generates
public final class CreateOrderRequest {
    private final UUID            customerId;      // required
    private final List<OrderItem> items;          // required, min 1
    private final Address        shippingAddress; // required
    private final PaymentMethod  paymentMethod;   // required
    private final String         couponCode;      // optional
    private final boolean        giftWrap;        // optional, default false
    private final String         giftMessage;     // optional, required if giftWrap=true
    private final Priority       priority;        // optional, default NORMAL

    private CreateOrderRequest(Builder b) {
        this.customerId      = Objects.requireNonNull(b.customerId, "customerId required");
        this.items           = List.copyOf(Objects.requireNonNull(b.items));
        this.shippingAddress = Objects.requireNonNull(b.shippingAddress);
        this.paymentMethod   = Objects.requireNonNull(b.paymentMethod);
        this.couponCode      = b.couponCode;
        this.giftWrap        = b.giftWrap;
        this.giftMessage     = b.giftMessage;
        this.priority        = b.priority != null ? b.priority : Priority.NORMAL;
        // Invariants validated ONCE at build time — object born fully valid
        if (items.isEmpty())            throw new IllegalArgumentException("Need 1+ items");
        if (giftWrap && giftMessage == null)
            throw new IllegalArgumentException("Gift wrap requires a message");
    }

    public static class Builder {
        private UUID customerId;    private List<OrderItem> items = new ArrayList<>();
        private Address shippingAddress; private PaymentMethod paymentMethod;
        private String couponCode;  private boolean giftWrap; private String giftMessage;
        private Priority priority;

        public Builder customerId(UUID v)           { this.customerId = v; return this; }
        public Builder addItem(OrderItem i)         { items.add(i); return this; }
        public Builder shippingAddress(Address v)   { this.shippingAddress = v; return this; }
        public Builder paymentMethod(PaymentMethod v){ this.paymentMethod = v; return this; }
        public Builder couponCode(String v)         { this.couponCode = v; return this; }
        public Builder giftWrap(String msg)          { giftWrap=true; giftMessage=msg; return this; }
        public Builder priority(Priority v)          { this.priority = v; return this; }
        public CreateOrderRequest build()             { return new CreateOrderRequest(this); }
    }
    public static Builder builder() { return new Builder(); }
}

// LOMBOK @Builder — generates all the above automatically
@Data @Builder @NoArgsConstructor @AllArgsConstructor
public class NotificationRequest {
    @NonNull  String             recipient;
    @NonNull  NotificationType   type;
    @Builder.Default String    priority   = "NORMAL";    // default value in builder
    @Builder.Default boolean   retryable  = true;
    @Builder.Default int       maxRetries = 3;
    Map<String, Object>          metadata;   // optional
    LocalDateTime               scheduledAt; // optional
}
// Lombok generates: inner Builder class, fluent setters, build() calling all-args ctor.
// @Builder.Default: adds boolean $fieldName$set flag; if not set, uses default value.
// @NonNull: adds null check inside the builder setter → throws NullPointerException early.

// Spring uses Builder internally everywhere:
return ResponseEntity.status(HttpStatus.CREATED)
    .header("Location", "/orders/" + order.getId())
    .eTag("\"" + order.getVersion() + "\"")
    .cacheControl(CacheControl.maxAge(60, TimeUnit.SECONDS))
    .body(orderDTO);

WebClient client = WebClient.builder()
    .baseUrl("https://api.razorpay.com/v1")
    .defaultHeader(HttpHeaders.AUTHORIZATION, "Basic " + base64Creds)
    .filter(retryFilter())
    .codecs(c -> c.defaultCodecs().maxInMemorySize(10 * 1024 * 1024))
    .build();
🎯 Interview Questions
Why use Builder for immutable objects? What does Lombok @Builder generate internally?
Immutability + Builder: Immutable objects need all fields final — set once in the constructor. With 10+ optional fields, you'd need exponential constructor overloads. Builder collects all settings in a mutable Builder object, validates invariants at build(), then calls the private constructor once. The result: all fields are final, object is fully valid from birth, never partially constructed, thread-safe by design.
Lombok @Builder generates: (1) Inner static Builder class with a field per class field. (2) Fluent setter returning this per field. (3) build() calling the all-args constructor. (4) Static builder() factory method. @Builder.Default adds a boolean $name$set flag — if the field was not explicitly set, builder constructor uses the default.
Day 5 · W1 Prototype Pattern Creational
Intent

Create new objects by cloning an existing prototype instead of instantiating from scratch. When creation is expensive (DB load, heavy config), clone a pre-configured template and customize the clone. The prototype registry manages the pool.

⚠️ Shallow vs Deep Copy — The Bug Everyone Ships super.clone() copies primitive fields + object references (NOT the objects). Modifying a List field in the clone also modifies the original prototype — corrupting all future clones from the registry. Always deep-copy every mutable field.
JavaEmail Template Registry + Shallow vs Deep Copy Explained
// Email template — expensive to load from DB, so we load once and clone per request
public class EmailTemplate implements Cloneable {
    private String              id;           // String immutable → safe shallow copy
    private String              subject;      // String immutable → safe shallow copy
    private String              bodyTemplate; // String immutable → safe shallow copy
    private List<String>        recipients;   // ⚠️ MUTABLE → MUST deep copy
    private Map<String,String> variables;    // ⚠️ MUTABLE → MUST deep copy

    @Override
    public EmailTemplate clone() {
        try {
            EmailTemplate copy = (EmailTemplate) super.clone(); // shallow first
            // Deep copy every mutable field to isolate clone from prototype:
            copy.recipients = new ArrayList<>(this.recipients); // new list, same elements
            copy.variables  = new HashMap<>(this.variables);    // new map, same entries
            return copy;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError("Implements Cloneable — cannot happen", e);
        }
    }
}

// Copy Constructor — preferred over Cloneable in modern Java (Bloch §13)
// No CloneNotSupportedException, explicit, safe, works with inheritance
public class ReportConfig {
    private final String             title;
    private final List<ReportColumn> columns;
    private final Map<String,Object> filters;

    // Copy constructor: explicit, no magic, easy to maintain
    public ReportConfig(ReportConfig src) {
        this.title   = src.title;                               // String: immutable ✅
        this.columns = src.columns.stream().map(ReportColumn::new).toList(); // deep ✅
        this.filters = new HashMap<>(src.filters);            // deep ✅
    }
    public static ReportConfig copyOf(ReportConfig src) { return new ReportConfig(src); }
}

// Prototype Registry — pool of pre-loaded templates
@Component
public class EmailTemplateRegistry {
    private final Map<String, EmailTemplate> pool = new ConcurrentHashMap<>();
    private final TemplateRepository repo;

    @PostConstruct
    public void loadTemplates() {
        // Load ALL templates from DB ONCE at startup (expensive)
        repo.findAll().forEach(t -> pool.put(t.getId(), t));
        log.info("Loaded {} templates", pool.size());
    }

    public EmailTemplate getTemplate(String id, Order order) {
        EmailTemplate proto = pool.get(id);
        if (proto == null) throw new IllegalArgumentException("Unknown template: " + id);

        EmailTemplate copy = proto.clone();  // cheap — no DB hit

        // Customize clone with order data — prototype is NEVER touched
        copy.getVariables().put("orderId",     order.getId().toString());
        copy.getVariables().put("customerName", order.getCustomerName());
        copy.getVariables().put("total",        order.getTotal().toString());
        copy.getRecipients().add(order.getCustomerEmail()); // own list, prototype safe
        return copy;
    }
}

// Spring prototype scope — new bean per injection
@Component
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE,
       proxyMode = ScopedProxyMode.TARGET_CLASS) // needed when injecting into singletons
public class RequestContext {
    private final Map<String,Object> attrs = new HashMap<>();
    private String correlationId = UUID.randomUUID().toString();
}
Field TypeShallow CloneDeep Needed?Fix
int / boolean / double✅ Copied by valueNosuper.clone() is fine
String✅ Safe (immutable)Nosuper.clone() is fine
Enum✅ Safe (singleton)Nosuper.clone() is fine
List / Set / Map❌ Shared reference — BUG!Yesnew ArrayList<>(original)
Custom mutable object❌ Shared reference — BUG!YesRecursive clone / copy constructor
Array❌ Shared reference — BUG!YesArrays.copyOf(arr, arr.length)
🎯 Interview Questions
Shallow vs deep copy — show a real bug example. Why does Bloch say avoid Cloneable?
Bug: EmailTemplate clone = proto.clone() with only super.clone(). Both proto.recipients and clone.recipients point to the SAME ArrayList. Calling clone.recipients.add("attacker@evil.com") also adds to proto.recipients — every future clone from the registry is now corrupted with the attacker's email.
Bloch's criticism: (1) Cloneable is a marker interface that doesn't declare clone() — contract is invisible. (2) Object.clone() is protected — you must override and widen to public. (3) CloneNotSupportedException is checked but can never throw — forces boilerplate try-catch. (4) Deep copy isn't compiler-enforced — developers miss it and ship bugs. Modern alternatives: Copy constructor new EmailTemplate(source), static factory EmailTemplate.copyOf(source).

Prototype vs Flyweight — what is the difference?
Prototype: CLONES — each client gets their OWN independent copy to modify freely. Client can add recipients, change variables, etc. without affecting others. Flyweight: SHARES — one IMMUTABLE object serves all clients. All clients read the same object; no one modifies it (String pool, Integer cache). Rule: If clients need to modify their copy → Prototype (clone). If the object is immutable and sharing saves memory → Flyweight.