S
🔧 Part 2 of 4 — Structural

GoF Structural
Design Patterns

7 structural patterns with production Java examples, Spring/Hibernate/JDK internals, deeper interview Q&A (3–5 questions each), and a dedicated "Used in Frameworks" section for every pattern.

Adapter — SOAP→REST, HttpMessageConverter Decorator — @Transactional, @Cacheable Facade — OrderFacade, DispatcherServlet Proxy — CGLIB, JDK proxy, AOP Composite — Permission tree, Spring Security Bridge — JDBC, Notification × Channel Flyweight — String pool, Integer cache, Hibernate L2C
7
Structural Patterns
35+
Code Snippets
30+
Interview Q&As
2/4
GoF Series
Day 6 · W1 Adapter Pattern Structural
Intent

Convert the interface of a class into another interface that clients expect. Make incompatible interfaces work together without modifying either side. Also called "Wrapper."

Adapter is the most common structural pattern in enterprise Java. Every time you wrap a third-party library in your own interface, you're writing an Adapter. The adapter translates between your domain's language and the external system's language.

Two forms: (1) Object Adapter — uses composition, wraps an instance of the adaptee. More flexible, the standard in Java. (2) Class Adapter — uses multiple inheritance, extends both target and adaptee. Not possible in Java (single inheritance). Use object adapter in Java always.

JavaLegacy SOAP → REST Adapter + AWS SDK Adapter
// TARGET: the interface your application works with
public interface PaymentGateway {
    PaymentResult  charge(UUID customerId, BigDecimal amount, String currency);
    RefundResult   refund(String transactionId, BigDecimal amount);
    PaymentStatus  getStatus(String transactionId);
}

// ADAPTEE: legacy SOAP client (can't modify — third-party jar)
// Different method names, different parameter types, different response model
public class LegacySoapPaymentClient {
    public SoapChargeResponse  chargeCustomer(String customerId, double amount, String currency) { return null; }
    public SoapRefundResponse  issueRefund(String txId, double refundAmt) { return null; }
    public SoapStatusResponse queryTxStatus(String txId) { return null; }
}

// ADAPTER: bridges SOAP client to PaymentGateway interface
// Selected when payment.provider=legacy via Spring conditional
@Service
@ConditionalOnProperty(name = "payment.provider", havingValue = "legacy")
public class SoapPaymentAdapter implements PaymentGateway {

    private final LegacySoapPaymentClient soapClient; // composed, not extended

    @Override
    public PaymentResult charge(UUID customerId, BigDecimal amount, String currency) {
        // Translate: UUID→String, BigDecimal→double, response→domain model
        SoapChargeResponse resp = soapClient.chargeCustomer(
            customerId.toString(),    // UUID → String
            amount.doubleValue(),    // BigDecimal → double
            currency
        );
        return PaymentResult.builder()
            .transactionId(resp.getTxnRef())
            .status(mapSoapStatus(resp.getResponseCode()))
            .amount(amount)
            .build();
    }

    @Override
    public RefundResult refund(String txId, BigDecimal amount) {
        SoapRefundResponse r = soapClient.issueRefund(txId, amount.doubleValue());
        return RefundResult.of(r.getRefundRef(), r.isSuccess());
    }

    @Override
    public PaymentStatus getStatus(String txId) {
        return mapSoapStatus(soapClient.queryTxStatus(txId).getCode());
    }

    private PaymentStatus mapSoapStatus(String code) {
        return switch (code) {
            case "00"       -> PaymentStatus.SUCCESS;
            case "01"       -> PaymentStatus.PENDING;
            case "05","51"  -> PaymentStatus.DECLINED;
            default         -> PaymentStatus.UNKNOWN;
        };
    }
}

// CLIENT: CheckoutService only knows PaymentGateway interface.
// Zero knowledge of SOAP, SoapChargeResponse, or string/double quirks.
@Service
public class CheckoutService {
    private final PaymentGateway gateway; // could be SOAP adapter or Razorpay or Stripe
    
    public CheckoutResult checkout(Order order) {
        PaymentResult result = gateway.charge(
            order.getCustomerId(), order.getTotal(), "INR");
        // ...
    }
}
🔩 Where Adapter Is Used in Java Frameworks
Spring MVC — HandlerAdapter
RequestMappingHandlerAdapter adapts @RequestMapping annotated methods to the HandlerAdapter interface. DispatcherServlet calls adapter.handle(req, res, handler) without knowing the handler type.
HttpMessageConverter
Adapts Java objects ↔ HTTP message body. MappingJackson2HttpMessageConverter adapts Java ↔ JSON. StringHttpMessageConverter adapts String ↔ plain text. Spring picks the right adapter based on Content-Type.
JpaVendorAdapter
HibernateJpaVendorAdapter adapts Hibernate-specific APIs to the JPA standard interface. Lets you swap ORM vendors (Hibernate ↔ EclipseLink) without touching application code.
Java Arrays.asList()
Adapts a plain Java array to the List interface. The array is the adaptee; the returned wrapper exposes List operations backed by the array.
InputStreamReader
Adapts InputStream (byte stream) to Reader (character stream). Classic Java I/O Adapter — converts the byte-oriented interface to the character-oriented one.
Spring WebFlux — HandlerFunctionAdapter
Adapts functional route handler functions (HandlerFunction<ServerResponse>) to the same HandlerAdapter interface used by annotation-based controllers.
🎯 Interview Questions — Adapter
Adapter vs Facade vs Decorator — explain the difference with one example each.
Adapter: Makes two incompatible interfaces work together. Changes the interface, keeps the behavior. Example: SoapPaymentAdapter implements PaymentGateway and delegates to LegacySoapClient.
Facade: Provides a simplified interface to a complex subsystem. Doesn't change interfaces — hides them. Example: OrderFacade.placeOrder() hides inventory + payment + shipping behind one call.
Decorator: Adds behavior to an existing interface without changing it. Wraps an object of the SAME interface. Example: LoggingOrderService implements OrderService and delegates to the real one. Key: Adapter changes the interface; Decorator and Facade don't.
Object Adapter vs Class Adapter — which is used in Java and why?
Object Adapter uses composition — holds an instance of the adaptee. Works with any adaptee, even subclasses. This is the standard Java approach.
Class Adapter uses multiple inheritance — extends both target and adaptee. Not possible in Java (single inheritance). However, Java can simulate it partially by implementing the target interface and extending the adaptee class.
Why Object Adapter wins: More flexible (can adapt any adaptee instance), no tight coupling through inheritance, allows the adaptee to be swapped at runtime, no limitations of single inheritance.
How does Spring's HttpMessageConverter use Adapter pattern? How does content negotiation work?
HttpMessageConverter is the target interface: canRead(Class, MediaType) and read(Class, HttpInputMessage). Each converter adapts a specific format — MappingJackson2HttpMessageConverter adapts JSON, Jaxb2RootElementHttpMessageConverter adapts XML, StringHttpMessageConverter adapts plain text.
During content negotiation: Spring walks through the registered converters list in order, calls canWrite(returnType, mediaType) on each, and uses the first one that returns true. This is the Chain of Responsibility + Adapter combination. Adding a custom format = implement HttpMessageConverter and register it — Spring picks it up automatically.
When would you use Adapter in a microservices migration?
The Anti-Corruption Layer (ACL) pattern in DDD is essentially a collection of Adapters. When migrating a monolith to microservices: (1) New service defines clean domain interfaces. (2) Adapters wrap calls to the legacy monolith's API/DB. (3) Legacy code is progressively replaced; adapters are removed as the migration completes. Example: LegacyOrderAdapter implements the new OrderRepository interface but queries the old monolith's Oracle database directly. Once the new service has its own DB, the adapter is replaced. The application code never knew the difference.
Day 7 · W1 Decorator Pattern Structural
Intent

Attach additional responsibilities to an object dynamically at runtime, without modifying the class. Decorators provide a flexible alternative to subclassing. The decorator implements the SAME interface as the component it wraps — client code is unaware.

The key insight: A Decorator wraps an object of the same interface type. Client code calls the same interface methods. The decorator adds behavior before/after delegating to the wrapped object. You can stack decorators: new Caching(new Logging(new Retry(realService))).

Spring uses Decorator everywhere — it IS Spring's AOP mechanism. @Transactional, @Cacheable, @Async, @Retryable all generate proxy subclasses that wrap your bean in decorator logic.

JavaOrderService Decorator Chain — Logging + Metrics + Retry + Caching
// Component Interface
public interface OrderService {
    Order        findById(UUID id);
    Order        createOrder(CreateOrderRequest req);
    List<Order>  listByCustomer(UUID customerId, Pageable page);
}

// Concrete Component
@Service
public class DefaultOrderService implements OrderService {
    private final OrderRepository repo;
    @Override
    public Order findById(UUID id) { return repo.findById(id).orElseThrow(); }
    @Override
    public Order createOrder(CreateOrderRequest req) { /* business logic */ return null; }
    @Override
    public List<Order> listByCustomer(UUID cid, Pageable p) { return repo.findByCustomerId(cid, p); }
}

// Decorator 1: Structured Logging with MDC correlation
public class LoggingOrderService implements OrderService {
    private final OrderService delegate; // wraps same interface ← the key
    private final Logger log = LoggerFactory.getLogger(getClass());

    @Override
    public Order findById(UUID id) {
        log.debug("findById called: id={}", id);
        long t = System.currentTimeMillis();
        try {
            Order result = delegate.findById(id); // delegate to wrapped object
            log.debug("findById ok: id={} elapsed={}ms", id, System.currentTimeMillis() - t);
            return result;
        } catch (Exception e) {
            log.error("findById failed: id={}", id, e); throw e;
        }
    }
    @Override
    public Order createOrder(CreateOrderRequest req) {
        log.info("createOrder: customerId={} items={}", req.getCustomerId(), req.getItems().size());
        Order order = delegate.createOrder(req);
        log.info("createOrder complete: orderId={}", order.getId());
        return order;
    }
    @Override
    public List<Order> listByCustomer(UUID cid, Pageable p) { return delegate.listByCustomer(cid, p); }
}

// Decorator 2: Micrometer Metrics
public class MetricsOrderService implements OrderService {
    private final OrderService  delegate;
    private final MeterRegistry registry;

    @Override
    public Order createOrder(CreateOrderRequest req) {
        return Timer.builder("order.create")
            .tag("payment", req.getPaymentMethod().name())
            .register(registry)
            .record(() -> delegate.createOrder(req));
    }
    @Override
    public Order findById(UUID id) { return delegate.findById(id); }
    @Override
    public List<Order> listByCustomer(UUID c, Pageable p) { return delegate.listByCustomer(c, p); }
}

// Wire decorators in @Configuration — outermost executes first
@Configuration
public class OrderServiceConfig {
    @Bean
    public OrderService orderService(OrderRepository repo, MeterRegistry registry) {
        OrderService core    = new DefaultOrderService(repo);
        OrderService logged  = new LoggingOrderService(core);
        return new MetricsOrderService(logged, registry);
        // Call chain: Metrics → Logging → DefaultOrderService → DB
    }
}

// How @Transactional IS Decorator — what Spring generates behind the scenes:
//
// class OrderService$$SpringCGLIB$$0 extends DefaultOrderService {
//   @Override
//   public Order createOrder(CreateOrderRequest req) {
//     TransactionStatus tx = txManager.getTransaction(txDef);  // before
//     try {
//       Order result = super.createOrder(req);                  // delegate
//       txManager.commit(tx);                                   // after-returning
//       return result;
//     } catch (RuntimeException e) {
//       txManager.rollback(tx);                                 // after-throwing
//       throw e;
//     }
//   }
// }
//
// @Cacheable wraps similarly:
//   1. Check cache → return if hit
//   2. Call super.findById(id) if miss
//   3. Store result in cache
//   4. Return result
🔩 Where Decorator Is Used in Java Frameworks
Spring @Transactional
CGLIB proxy wraps the method: begins TX before, commits/rolls back after. The bean itself is unchanged. This is Decorator pattern generated at startup.
Spring @Cacheable / @CacheEvict
AOP proxy wraps the method: checks cache before, stores result after. Same decoration pattern — the real method is the delegate.
Spring @Async
Proxy wraps the method call, submits it to a thread pool executor, and returns immediately. The real work runs asynchronously.
Resilience4j @CircuitBreaker
Proxy wraps the method: records call result, transitions circuit state. On open circuit: fails fast before calling the real method.
Java I/O Streams
BufferedInputStream(new FileInputStream(f)) decorates a FileInputStream with buffering. GZIPOutputStream(new FileOutputStream(f)) adds compression. Classic textbook Decorator.
HttpServletRequestWrapper
Spring Security's SecurityContextHolderAwareRequestWrapper decorates the raw request to add getUserPrincipal(), isUserInRole() backed by SecurityContext.
🎯 Interview Questions — Decorator
Decorator vs Inheritance — why choose Decorator to add behavior?
  • Open/Closed Principle: Add behavior without modifying the existing class.
  • Single Responsibility: Each decorator handles one concern — logging, metrics, retry, caching. No bloated superclass.
  • Runtime composition: Stack any combination at runtime (or in config). With inheritance you'd need LoggingRetryingCachingOrderService.
  • Works with final classes: Can't subclass, but can wrap them.
  • Open stack: Can add a new decorator without touching any existing class. With inheritance, you'd have to create new subclasses for every combination.
How does @Transactional use Decorator internally? What is self-invocation problem?
Mechanism: Spring generates a CGLIB proxy class that extends your bean class (or JDK proxy if you use an interface). The proxy method: begins TX, calls super.yourMethod(), commits/rolls back. The annotation metadata drives what TX propagation, isolation, and timeout to use.
Self-invocation bug: If method A() calls method B() on the same bean, and B() is @Transactional, it WON'T create a new transaction. The call goes directly to this.B(), bypassing the proxy. Fix: inject self (@Autowired OrderService self) and call self.B(), OR use AopContext.currentProxy(), OR restructure to avoid self-invocation.
Can you stack multiple Spring AOP annotations? What order do they execute?
Yes. When you have @Transactional @Cacheable @CircuitBreaker on a method, Spring creates nested proxy wrappers. The order is controlled by @Order on the aspect or the default precedence. Default order (outer to inner): @Transactional@Cacheable@CircuitBreaker → real method. So TX wraps the cache check — if cache returns a hit, no TX is started (efficient). If cache misses, TX is opened, method runs, result is cached in the same TX. Customize with @EnableCaching(order = ...) or @EnableTransactionManagement(order = ...).
Java I/O streams use Decorator — how does it work?
InputStream is the Component interface. FileInputStream is the Concrete Component. FilterInputStream is the abstract Decorator base. BufferedInputStream and GZIPInputStream are concrete decorators.
Stack: new DataInputStream(new BufferedInputStream(new GZIPInputStream(new FileInputStream("file.gz")))). Each layer wraps the next, adding: file access → decompression → buffering → typed reads. Client calls dis.readInt() and the call chain goes through all layers. This is why Java I/O is often cited as the canonical Decorator example.
Day 8 · W2 Facade Pattern Structural
Intent

Provide a simplified, unified interface to a complex subsystem. The facade hides complexity — clients call one facade method instead of orchestrating multiple subsystems in the right order.

JavaOrderFacade — 5 Subsystems Hidden Behind One Method
// Subsystems — each is a standalone @Service with its own logic
interface InventoryService { void   reserve(List<OrderItem> items); void release(UUID orderId); }
interface PaymentService   { String charge(UUID customerId, BigDecimal amount); }
interface ShippingService  { String schedule(UUID orderId, Address addr); }
interface NotifyService    { void   sendConfirmation(UUID customerId, Order order); }
interface LoyaltyService   { void   award(UUID customerId, BigDecimal total); }

// FACADE — one method orchestrates everything
@Service
@Slf4j
@RequiredArgsConstructor
@Transactional
public class OrderFacade {
    private final InventoryService inventory;
    private final PaymentService   payment;
    private final ShippingService  shipping;
    private final NotifyService    notify;
    private final LoyaltyService   loyalty;
    private final OrderRepository  repo;

    public OrderConfirmation placeOrder(CreateOrderRequest req) {
        // Step 1: Reserve stock (throws if out of stock)
        inventory.reserve(req.getItems());

        // Step 2: Persist order
        Order order = repo.save(Order.from(req));

        // Step 3: Charge payment — compensate on failure
        String txId;
        try {
            txId = payment.charge(req.getCustomerId(), order.getTotal());
        } catch (PaymentDeclinedException e) {
            inventory.release(order.getId()); // compensate
            throw e;
        }

        // Step 4: Schedule shipping
        String trackingCode = shipping.schedule(order.getId(), req.getAddress());

        // Step 5: Notify (best-effort — don't fail order if email fails)
        try { notify.sendConfirmation(req.getCustomerId(), order); }
        catch (Exception e) { log.warn("Notification failed, order still placed", e); }

        // Step 6: Award loyalty points (best-effort)
        loyalty.award(req.getCustomerId(), order.getTotal());

        return OrderConfirmation.of(order.getId(), trackingCode, txId);
    }
}

// CONTROLLER: only talks to the Facade — knows nothing about subsystems
@RestController
@RequestMapping("/v2/orders")
@RequiredArgsConstructor
public class OrderController {
    private final OrderFacade facade; // only dependency

    @PostMapping
    public ResponseEntity<OrderConfirmation> placeOrder(@Valid @RequestBody CreateOrderRequest req) {
        return ResponseEntity.status(201).body(facade.placeOrder(req));
    }
}
🔩 Where Facade Is Used in Java Frameworks
Spring JdbcTemplate
Facade over raw JDBC: hides getConnection(), prepareStatement(), exception translation, ResultSet iteration, and close() lifecycle. You call jdbcTemplate.query(sql, mapper).
SLF4J Logger
SLF4J is literally a logging facade. LoggerFactory.getLogger() returns a Facade that delegates to the actual binding (Logback, Log4j2, JUL) at runtime. Application code never knows which logging framework is underneath.
javax.faces.context.FacesContext
JSF's central facade providing access to request, response, session, application scope, message queue, navigation — all through one object.
Spring ApplicationContext
Facade over BeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver, EnvironmentCapable. You call ctx.getBean() without knowing the underlying machinery.
Hibernate Session
Facade over connection management, identity map, dirty checking, SQL generation, batching, caching. session.save(entity) triggers a pipeline of internal operations you never see.
RestTemplate / WebClient
Facade over HttpURLConnection / Netty: handles serialization, content negotiation, error response translation, retry — all behind restTemplate.getForObject(url, Dto.class).
🎯 Interview Questions — Facade
Facade vs Mediator — what is the architectural difference?
Facade: One-directional simplification. External clients call the facade; subsystems don't know about each other or about the facade. The relationship is: Client → Facade → Subsystems. Subsystems can work independently — the facade just orchestrates them.
Mediator: Bidirectional coordination between peers. Objects register with the mediator and communicate through it instead of directly. The mediator has logic to route messages. Example: Spring's DispatcherServlet is a Mediator — Controllers, ViewResolvers, and HandlerMappings all communicate through it, not directly with each other.
Rule: Facade = simplify complex subsystem access from outside. Mediator = reduce coupling between peer objects that need to interact with each other.
How does SLF4J implement Facade pattern? What are the benefits?
Logger is the Target interface (Facade). LoggerFactory returns the appropriate implementation at runtime. The actual logging implementation (Logback, Log4j2, JUL) is the subsystem. StaticLoggerBinder bridges SLF4J to the actual binding via classpath detection.
Benefits: (1) Your library code depends on SLF4J API only — users can choose their logging backend. (2) Switching Logback to Log4j2 = swap JAR, zero code changes. (3) One consistent API regardless of backend. (4) NDC/MDC operations work the same across all backends. This is exactly why every well-behaved Java library uses SLF4J rather than a concrete logging framework.
When does a Facade become an anti-pattern?
Facade becomes a God Object anti-pattern when: (1) It accumulates too many responsibilities — becomes a dumping ground for all business logic. (2) It violates SRP — one facade class handling orders, payments, inventory, shipping, customers. (3) It grows to 2000+ lines with dozens of methods. (4) It becomes tightly coupled to every subsystem, making it untestable. Fix: Split by domain context. Have an OrderFacade (order lifecycle), CustomerFacade (customer operations), InventoryFacade (stock management). Each facade orchestrates a bounded set of subsystems within its domain.
Day 9 · W2 Proxy Pattern Structural
Intent

Provide a surrogate or placeholder to control access to another object. The proxy implements the same interface, intercepts calls, and adds behavior (access control, lazy loading, caching, logging) before/after delegating to the real object.

Three types of Proxy: (1) Virtual Proxy — delays expensive object creation until first use (Hibernate lazy loading). (2) Protection Proxy — controls access based on permissions (Spring Security). (3) Remote Proxy — represents an object in a different JVM/process (Feign clients, RMI). Spring AOP uses Proxy as its foundation mechanism.

JavaManual JDK Proxy + CGLIB + Hibernate Lazy Proxy + Spring AOP
// MANUAL JDK DYNAMIC PROXY — requires target to implement an interface
public class AuditInvocationHandler implements InvocationHandler {
    private final Object      target;
    private final AuditLogger auditLog;

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String user = SecurityContextHolder.getContext().getAuthentication().getName();
        auditLog.record("CALL {} on {} by {}", method.getName(),
            target.getClass().getSimpleName(), user);
        long start = System.nanoTime();
        try {
            Object result = method.invoke(target, args); // delegate to real object
            auditLog.record("OK {} in {}ms", method.getName(), (System.nanoTime() - start) / 1_000_000);
            return result;
        } catch (InvocationTargetException e) {
            auditLog.record("FAIL {} threw {}", method.getName(), e.getCause().getClass().getSimpleName());
            throw e.getCause();
        }
    }
}

// Create JDK proxy — the proxy object IS the same type as the interface
OrderService proxy = (OrderService) Proxy.newProxyInstance(
    OrderService.class.getClassLoader(),
    new Class[]{ OrderService.class },
    new AuditInvocationHandler(realOrderService, auditLog)
);

// SPRING AOP @Aspect — the clean production way (Spring generates proxy for you)
@Aspect
@Component
public class SecurityAuditAspect {
    // Around advice on any @Service method annotated with @Audited
    @Around("@annotation(audited)")
    public Object auditMethod(ProceedingJoinPoint pjp, Audited audited) throws Throwable {
        String user   = SecurityContextHolder.getContext().getAuthentication().getName();
        String method = pjp.getSignature().toShortString();
        log.info("AUDIT: {} called {} args={}", user, method, pjp.getArgs());
        try {
            Object result = pjp.proceed(); // call the real method
            log.info("AUDIT: {} completed", method);
            return result;
        } catch (Throwable t) {
            log.error("AUDIT: {} failed", method, t); throw t;
        }
    }
}

// HIBERNATE LAZY PROXY — Virtual Proxy example
@Entity
public class Order {
    @Id @GeneratedValue private UUID id;

    // Hibernate creates a PersistentBag PROXY for this collection.
    // The actual SELECT runs only when items is accessed — not at Order load time.
    @OneToMany(mappedBy = "order", fetch = FetchType.LAZY)
    private List<OrderItem> items; // PersistentBag proxy, NOT ArrayList
}
// order.getItems()       → triggers SELECT * FROM order_items WHERE order_id=?
// Accessing outside @Transactional → LazyInitializationException!

// SPRING @Lazy — Virtual Proxy for beans
@Service
public class HeavyAnalyticsService {
    public HeavyAnalyticsService() {
        // expensive initialization: loads ML model, connects to Spark, etc.
    }
}

@Service
public class OrderService {
    @Lazy  // Spring creates a PROXY. Real HeavyAnalyticsService init deferred until first call.
    private final HeavyAnalyticsService analytics;
    // HeavyAnalyticsService won't be initialized until analytics.someMethod() is actually called
}
🔩 Where Proxy Is Used in Java Frameworks
Spring AOP (CGLIB + JDK)
All AOP advice (@Before, @After, @Around) generates proxy classes. CGLIB for concrete classes, JDK proxy for interface-based beans. This is the backbone of @Transactional, @Cacheable, @Secured.
Hibernate Lazy Loading
Every @OneToMany(fetch=LAZY) association is backed by a Hibernate proxy (PersistentBag / PersistentSet). The SQL fires only on first access — Virtual Proxy pattern.
Spring Data JPA Repositories
JpaRepository<Order, UUID> is an interface. Spring Data generates a JDK proxy implementation at startup via JdkDynamicAopProxy. The proxy routes calls to SimpleJpaRepository for standard methods and to custom implementations for others.
Feign / OpenFeign
Feign clients are JDK proxies. @FeignClient interface PaymentServiceClient — Feign generates a proxy that translates interface method calls to HTTP requests. You call client.charge(), the proxy makes an HTTP POST.
Spring @Scope("prototype") injection
When injecting a prototype or request-scoped bean into a singleton, Spring injects a scoped proxy. Each method call on the proxy creates/retrieves the appropriate scoped instance.
MyBatis Mapper
MyBatis generates JDK proxies for all @Mapper interfaces. The proxy translates method calls to SQL statements mapped in XML or annotations.
🎯 Interview Questions — Proxy
JDK Dynamic Proxy vs CGLIB — which is used when in Spring?
JDK Proxy: Used when the bean implements at least one interface. Creates a proxy object that implements the same interface(s). Uses java.lang.reflect.Proxy + InvocationHandler. Method dispatch via reflection.
CGLIB: Used when the bean is a concrete class with no interface. Generates a bytecode subclass at startup. Faster than JDK proxy post-Java 8. Cannot proxy final classes or final methods.
Spring Boot default (since Spring 5.2): CGLIB is preferred even for interface-based beans to avoid proxy type-casting issues. Override with spring.aop.proxy-target-class=false.
Limitations of both: Private methods are NOT proxied (proxy can't intercept them). Same-class self-invocation bypasses the proxy entirely.
What is LazyInitializationException and how do you fix it?
Hibernate's lazy proxy fires a SQL query to load the association the first time the collection is accessed. If this happens outside a @Transactional context (the Hibernate session is closed), it throws LazyInitializationException: could not initialize proxy - no Session.
Fixes: (1) Access the association within the @Transactional method. (2) Use JOIN FETCH in JPQL: SELECT o FROM Order o JOIN FETCH o.items WHERE o.id = :id. (3) Use @EntityGraph(attributePaths = "items") on the repository method. (4) Use FetchType.EAGER (careful — N+1 problem). (5) Use DTO projections with Spring Data. Best practice: Never use EAGER globally. Use JOIN FETCH or @EntityGraph per query where you need the association.
Proxy vs Decorator — they look identical in code. What's the difference?
Intent is the key difference, not structure:
Decorator — adds behavior to an object chosen by the client. Client typically creates the decorator chain. Focus is on enriching functionality. The wrapped object IS the business logic.
Proxy — controls ACCESS to an object. Client doesn't create the proxy — it's created transparently by a framework. Focus is on access control, lazy init, remote access, caching. The client doesn't know it's talking to a proxy vs the real object.
Structurally: both implement the same interface and hold a reference to the wrapped object. The distinction is semantic and intent-based. Spring's @Transactional generates a Proxy (access control/TX management), not a Decorator, even though the code structure looks similar.
What is the @Transactional self-invocation problem and how do you solve it?
Problem: Method A() in OrderService calls B() in the same class. B() is @Transactional(propagation = REQUIRES_NEW). The call goes to this.B() — bypassing the Spring proxy. The new transaction is never created.
Reason: The proxy wraps the bean. External callers go through the proxy. But when A() calls B() on this, it calls directly on the real object, not the proxy.
Solutions: (1) Inject self: @Autowired private OrderService self; then call self.B(). (2) AopContext.currentProxy() — call ((OrderService) AopContext.currentProxy()).B() — requires @EnableAspectJAutoProxy(exposeProxy = true). (3) Best solution: refactor — move B() to a separate @Service class. Self-invocation is a design smell indicating too much in one class.
Day 10 · W2 Composite Pattern Structural
Intent

Compose objects into tree structures for part-whole hierarchies. Composite lets clients treat individual objects (Leaf) and compositions (Composite) uniformly through the same interface. One render() call traverses the entire tree.

JavaRBAC Permission Tree — AND/OR Composite + Spring Security
// Component: common interface for leaf and composite
public interface Permission {
    boolean isGranted(UserContext user, String resource);
    String  describe();
}

// LEAF: atomic permission — no children
public class RolePermission implements Permission {
    private final String requiredRole;
    @Override
    public boolean isGranted(UserContext user, String resource) {
        return user.getRoles().contains(requiredRole);
    }
    @Override public String describe() { return "HAS_ROLE(" + requiredRole + ")"; }
}

public class OwnerPermission implements Permission {
    private final ResourceOwnerResolver resolver;
    @Override
    public boolean isGranted(UserContext user, String resource) {
        return resolver.isOwner(user.getId(), resource);
    }
    @Override public String describe() { return "IS_OWNER"; }
}

// COMPOSITE: AND — all children must be granted
public class AllOfPermission implements Permission {
    private final List<Permission> children = new ArrayList<>();
    private final String label;

    public AllOfPermission add(Permission p) { children.add(p); return this; }
    @Override
    public boolean isGranted(UserContext user, String resource) {
        return children.stream().allMatch(p -> p.isGranted(user, resource)); // AND
    }
    @Override
    public String describe() {
        return "ALL_OF(" + children.stream().map(Permission::describe).collect(Collectors.joining(",")) + ")";
    }
}

// COMPOSITE: OR — any child being granted is sufficient
public class AnyOfPermission implements Permission {
    private final List<Permission> children = new ArrayList<>();
    @Override
    public boolean isGranted(UserContext user, String resource) {
        return children.stream().anyMatch(p -> p.isGranted(user, resource)); // OR
    }
    public AnyOfPermission add(Permission p) { children.add(p); return this; }
    @Override public String describe() { return "ANY_OF(...)"; }
}

// Build complex permission tree:
// canAccessAdminOrders = (ROLE_ADMIN OR ROLE_SUPERVISOR) AND (ORDER_ACCESS AND IS_OWNER)
Permission canAccess = new AllOfPermission("adminAccess")
    .add(new AnyOfPermission("roleCheck")
        .add(new RolePermission("ROLE_ADMIN"))
        .add(new RolePermission("ROLE_SUPERVISOR")))
    .add(new AllOfPermission("resourceCheck")
        .add(new RolePermission("ORDER_ACCESS"))
        .add(new OwnerPermission(ownerResolver)));

// Client: same call regardless of tree depth — Composite shines here
boolean allowed = canAccess.isGranted(userCtx, "/orders/123");
System.out.println(canAccess.describe()); // ALL_OF(ANY_OF(HAS_ROLE(ROLE_ADMIN),...),ALL_OF(...))
🔩 Where Composite Is Used in Java Frameworks
Spring Security RoleHierarchy
RoleHierarchyImpl: ROLE_ADMIN > ROLE_MANAGER > ROLE_USER. Admin automatically includes all subordinate roles. Tree traversal determines effective authorities. This IS Composite — leaf roles and composite role groups treated uniformly.
Spring CompositeFilter
CompositeFilter holds a list of filters but implements Filter itself. Client code sees one Filter, but internally it delegates to N filters in sequence. Classic Composite — leaf and branch both implement the same interface.
Bean Validation — constraint groups
Composite constraints: @NotBlank @Email @Size(max=100) combined in a custom @ValidEmail annotation. ConstraintValidator delegates to child validators. Validation tree traversal with @Valid on nested objects.
Swing / JavaFX UI tree
Every JComponent.paint(Graphics) call recursively calls paint on all children. JPanel is a Composite containing Buttons (leaves). One call at the root repaints the entire window.
File system (java.io.File)
File.listFiles() returns both files and directories. A recursive delete: if it's a file, delete it; if it's a directory, recursively delete all children. Both file and directory implement the same File interface — Composite in action.
🎯 Interview Questions — Composite
When would you use Composite pattern? What problem does it solve?
Use Composite when: (1) You have tree/hierarchy structures where nodes can be either leaves or containers of other nodes. (2) Client code should treat individual objects and groups uniformly — same method call on a leaf or composite. (3) You want to avoid instanceof checks and branching in traversal code.
Problems it solves: Without Composite, you'd write: if (item instanceof File) delete(file); else if (item instanceof Directory) { for each child delete recursively; }. With Composite: item.delete() — the item knows whether to delete itself or recurse. Client code is clean.
Composite vs Iterator — how do they relate in tree traversal?
Composite defines the tree structure and leaf/node behavior. Iterator provides sequential access to traverse that structure. They complement each other — Composite for structure, Iterator for traversal. In Java, a FileSystemComponent Composite can implement Iterable to provide depth-first or breadth-first traversal via an Iterator. Spring's CompositeFilter iterates its child filters in order — combining Composite structure with Iterator-style execution.
How does Spring Security's RoleHierarchy use Composite? How does it affect @PreAuthorize?
RoleHierarchy defines: ROLE_ADMIN > ROLE_MANAGER > ROLE_USER. Internally, RoleHierarchyImpl builds a graph of role reachability. When Spring Security evaluates @PreAuthorize("hasRole('ROLE_MANAGER')"), it calls roleHierarchy.getReachableGrantedAuthorities() which traverses the hierarchy tree and returns all roles reachable from the user's granted roles. An admin gets ADMIN + MANAGER + USER effective roles. @Bean RoleHierarchy must be injected into DefaultMethodSecurityExpressionHandler for it to apply to @PreAuthorize.
Day 11 · W2 Bridge Pattern Structural
Intent

Decouple an abstraction from its implementation so the two can vary independently. Instead of a deep inheritance hierarchy (N×M subclasses), use composition — the abstraction holds a reference to the implementation (the bridge).

The class explosion problem: 3 notification types (Order, Marketing, Alert) × 3 channels (Email, SMS, Push) = 9 subclasses without Bridge. Add WhatsApp channel = 3 more subclasses, 12 total. With Bridge: 3 notification classes + 3 channel classes = 6. Adding WhatsApp = 1 new channel class. They vary independently.

JDBC is the canonical Bridge: java.sql.Connection = abstraction. Each database driver (PgConnection, MySQLConnection) = implementation. Your code uses the Connection API; the driver handles SQL dialect.

JavaNotification Bridge — Type × Channel Independence + JDBC Bridge
// IMPLEMENTATION INTERFACE (the "implementor" side of the bridge)
public interface NotificationChannel {
    void   send(String recipient, String title, String content);
    String getChannelName();
    boolean supportsRichContent();
}

// Concrete implementations (vary independently)
@Component
public class EmailChannel implements NotificationChannel {
    private final JavaMailSender mail;
    @Override
    public void send(String to, String subject, String html) {
        MimeMessage msg = mail.createMimeMessage();
        new MimeMessageHelper(msg, true).setTo(to);
        mail.send(msg);
    }
    public String getChannelName() { return "EMAIL"; }
    public boolean supportsRichContent() { return true; }
}

@Component
public class SmsChannel implements NotificationChannel {
    private final TwilioClient twilio;
    @Override
    public void send(String phone, String title, String text) {
        twilio.messages().create(phone, Message.creator(phone, fromNumber, title + ": " + text));
    }
    public String getChannelName() { return "SMS"; }
    public boolean supportsRichContent() { return false; }
}

// ABSTRACTION (uses the channel bridge via composition)
public abstract class Notification {
    protected final NotificationChannel channel; // THE BRIDGE — composition, not inheritance
    protected Notification(NotificationChannel channel) { this.channel = channel; }
    public abstract void send(String recipient, String message);
}

// Refined Abstractions (vary independently of channel)
public class UrgentNotification extends Notification {
    public UrgentNotification(NotificationChannel ch) { super(ch); }
    @Override
    public void send(String recipient, String message) {
        channel.send(recipient, "🚨 URGENT", message);
    }
}

public class ScheduledNotification extends Notification {
    private final LocalDateTime at;
    public ScheduledNotification(NotificationChannel ch, LocalDateTime at) { super(ch); this.at = at; }
    @Override
    public void send(String recipient, String message) {
        scheduler.schedule(() -> channel.send(recipient, "Reminder", message), at);
    }
}

// Usage: mix any abstraction with any channel — no subclass explosion
new UrgentNotification(emailChannel).send("admin@co.com", "Payment failed!");
new UrgentNotification(smsChannel).send("+91-9876543210", "Server down!");
new ScheduledNotification(pushChannel, tomorrow).send(deviceToken, "Daily report ready");
// 3 channels × 3 abstraction types = 6 classes (not 9)
// Adding WhatsApp: 1 new class. Zero changes to notification abstractions.

// JDBC — the canonical Bridge example
// Abstraction: java.sql.Connection
// Implementation: org.postgresql.jdbc.PgConnection (PostgreSQL driver)
//                 com.mysql.cj.jdbc.ConnectionImpl (MySQL driver)
Connection conn = DriverManager.getConnection("jdbc:postgresql://localhost/orders");
// conn is actually PgConnection, but your code sees only Connection
conn.prepareStatement("SELECT * FROM orders"); // same API for ALL databases
🔩 Where Bridge Is Used in Java Frameworks
JDBC DriverManager
Connection, Statement, ResultSet are the abstractions. Each database driver provides concrete implementations. Your SQL code works with the abstraction, never touching PostgreSQL or MySQL specifics.
Spring DataSource abstraction
DataSource (abstraction) can be backed by HikariCP, DBCP2, Tomcat pool, or embedded H2. Spring code calls dataSource.getConnection(); the underlying pool implementation is the bridge.
Logging Bridge — SLF4J
SLF4J API is the abstraction. Logback, Log4j2, JUL are implementations. The bridge is the SLF4JServiceProvider binding. Application uses SLF4J API; the implementation handles actual file writing.
Spring PlatformTransactionManager
Abstraction for transaction management. Concrete impls: JpaTransactionManager, DataSourceTransactionManager, JtaTransactionManager. @Transactional works with all of them via the bridge interface.
🎯 Interview Questions — Bridge
Bridge vs Strategy — both use composition. What's the difference?
Bridge: Structural pattern. Separates abstraction from implementation so BOTH can vary independently. The abstraction class IS-A abstraction; it HAS-A implementation via composition (the bridge). The focus is on structural decomposition to avoid class explosion. Example: Notification (abstraction hierarchy) + Channel (implementation hierarchy) — both have their own class trees.
Strategy: Behavioral pattern. Defines a family of interchangeable algorithms that are swapped at runtime. The context object has ONE strategy that defines HOW to do something. Focus is on behavioral variation. Example: PaymentService has a DiscountStrategy that can be swapped.
Key: Bridge has two independent class hierarchies. Strategy has one context with one interchangeable piece. Bridge is about structure; Strategy is about behavior.
How does JDBC implement Bridge? Why can you switch databases without code changes?
java.sql.Connection, Statement, ResultSet are the abstraction interfaces (defined in java.sql package in JDK). Each DB driver JAR provides concrete implementations: org.postgresql.jdbc.PgConnection, com.mysql.cj.jdbc.ConnectionImpl, etc. DriverManager.getConnection(url) loads the appropriate driver class via JDBC URL prefix and returns the concrete implementation — but your code only sees Connection.
To switch from PostgreSQL to MySQL: change JDBC URL + swap driver JAR. Zero application code changes. This is the Bridge benefit — abstraction layer insulates your code from implementation specifics.
Day 12 · W2 Flyweight Pattern Structural
Intent

Share intrinsic (shared, immutable) state among many fine-grained objects to reduce memory. Each flyweight stores intrinsic state only; extrinsic (context-specific) state is passed in per call. One shared object serves many client contexts.

Intrinsic vs Extrinsic: Intrinsic state = shared, context-independent (a product's category metadata, a font's name and style). Extrinsic state = unique per use (a product's current price, a character's position on screen). Flyweight stores only intrinsic state; extrinsic state is passed in when needed.

JavaProduct Category Flyweight + Java String Pool + Integer Cache
// FLYWEIGHT: immutable shared state (heavy category metadata loaded once from DB)
public final class CategoryFlyweight {
    // INTRINSIC: shared by all products in this category
    private final String              categoryCode;
    private final String              displayName;
    private final List<String>        allowedAttributes;
    private final Map<String,Object>  taxRules;           // complex, expensive
    private final ShippingProfile     shippingProfile;

    // Operates with extrinsic state passed in per call — flyweight itself is IMMUTABLE
    public BigDecimal calculateTax(BigDecimal price,       // extrinsic
                                     String customerRegion) { // extrinsic
        var rate = (BigDecimal) taxRules.get(customerRegion);
        return rate != null ? price.multiply(rate) : BigDecimal.ZERO;
    }

    public boolean requiresSignature(BigDecimal declaredValue) { // extrinsic
        return shippingProfile.getSignatureThreshold().compareTo(declaredValue) <= 0;
    }
}

// FLYWEIGHT FACTORY: maintains the pool
@Component
public class CategoryFlyweightFactory {
    private final ConcurrentHashMap<String, CategoryFlyweight> pool = new ConcurrentHashMap<>();
    private final CategoryRepository repo;

    public CategoryFlyweight get(String code) {
        return pool.computeIfAbsent(code, k -> {
            log.debug("Loading category flyweight: {}", k);
            return new CategoryFlyweight(repo.findByCode(k)); // DB load once per category
        });
    }
}

// Product: 1 million products share 50 flyweight category objects
public class Product {
    private final UUID                id;
    private final String              name;
    private       BigDecimal          price;        // extrinsic (product-specific)
    private final CategoryFlyweight   category;     // SHARED flyweight — one per category

    // Memory: 1M products × 50 categories = 1M Product shells + 50 flyweights
    // Without flyweight: 1M products × 5KB category data = 5GB memory!
    // With flyweight: 50 × 5KB = 250KB + 1M lightweight Product objects

    public BigDecimal calculateTax(String region) {
        return category.calculateTax(price, region); // pass extrinsic state
    }
}

// JAVA BUILT-IN FLYWEIGHTS:

// 1. String pool — JVM maintains one instance per unique string literal
String s1 = "hello";           // interned in String pool
String s2 = "hello";           // returns same pool entry
System.out.println(s1 == s2);  // true! same object

String s3 = new String("hello"); // new heap object, bypasses pool
System.out.println(s1 == s3);    // false — different objects
System.out.println(s3.intern() == s1); // true — intern() returns pool entry

// 2. Integer.valueOf() cache: -128 to 127 are cached (JLS mandated)
// The range was chosen as most-commonly used integers in programs.
// Can extend upper bound with -XX:AutoBoxCacheMax=N JVM flag.
Integer a = Integer.valueOf(100); // from cache
Integer b = Integer.valueOf(100); // same cached instance
System.out.println(a == b);        // TRUE ← cached!

Integer c = Integer.valueOf(500); // outside cache range
Integer d = Integer.valueOf(500); // new object
System.out.println(c == d);        // FALSE ← NOT cached!
// ALWAYS use .equals() for Integer comparison — never == !

// 3. Hibernate 2nd Level Cache — Flyweight-inspired
// Entity instances keyed by primary key are cached and shared across sessions.
@Entity
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY) // shared across ALL sessions
public class Country { ... } // 200 countries → 200 cached flyweight-like objects
🔩 Where Flyweight Is Used in Java Frameworks
Java String Pool
JVM maintains one String object per unique string literal. All "hello" literals share the same object. The String pool IS the flyweight factory. Immutability is what makes sharing safe.
Integer / Long / Boolean Cache
Integer.valueOf(-128 to 127) returns cached instances. Boolean.TRUE, Boolean.FALSE, Byte.valueOf() are all flyweights. Auto-boxing uses valueOf() — boxing 100 into Integer always returns the cached instance.
Hibernate 2nd Level Cache
Entities marked @Cache(READ_ONLY) are stored in a cache shared across sessions and even JVM instances (with Hazelcast/Redis cache provider). Same entity ID → same cached object.
Spring Bean Container
Every singleton @Bean is a flyweight — shared across the entire application. Many objects hold a reference to the same OrderService bean. The bean's intrinsic state (config, dependencies) is shared; extrinsic state (method arguments) comes per call.
java.util.concurrent.Executors
Thread objects are expensive. ThreadPool reuses (shares) thread objects across tasks. The thread is the flyweight — its intrinsic state is the thread itself; the task (Runnable) is the extrinsic state passed per execution.
🎯 Interview Questions — Flyweight
String pool — is it Flyweight? Why does new String("hello") bypass the pool?
Yes — String pool is the canonical Java Flyweight. The pool is the flyweight factory (maintained by JVM in native memory post-Java 7). Each unique string value has one String object in the pool. String literals are automatically interned at compile time. The immutability of String is what makes sharing safe — no client can modify the shared object.
new String("hello") explicitly creates a new heap object, bypassing the pool. This is almost never what you want. Use s.intern() to add a heap string to the pool and get back the canonical reference. Bottom line: for constant strings, always use literals "hello" over new String("hello").
Integer.valueOf(-128 to 127) — why this range? What's the Integer comparison trap?
Why this range: Java Language Specification §5.1.7 mandates that boxing of values from -128 to 127 returns the same object. The range was chosen based on analysis of common Java programs — these integers appear most frequently. The upper bound can be extended with -XX:AutoBoxCacheMax=N JVM flag.
The trap: Integer a = 200; Integer b = 200; a == b is false — two different heap objects. Integer a = 100; Integer b = 100; a == b is true — cached. This inconsistency causes subtle bugs. Rule: ALWAYS use a.equals(b) for Integer comparison. NEVER use == for boxed types. This is the most common Java interview trap question.
Flyweight vs Singleton vs Prototype — when to use each?
Singleton: Exactly ONE instance of a class exists. Used for stateful services (connection pool, config). The instance IS the business object.
Flyweight: A POOL of shared immutable objects, one per unique intrinsic state. Used when you have many fine-grained objects with shared state. Memory optimization. The object is data, not a service.
Prototype: CLONE existing objects. Used when object creation is expensive and you want independent copies that can be modified. Each client gets their own copy.
When to use: Service that must be shared globally → Singleton. Many objects sharing heavy metadata → Flyweight. Template objects that need customization → Prototype.
How does Hibernate 2nd Level Cache relate to Flyweight? What are the caveats?
Hibernate L2 cache stores entity state keyed by primary key. Multiple sessions requesting the same entity ID get the same cached representation — this is Flyweight (shared intrinsic state per ID). Works best for READ_ONLY or NONSTRICT_READ_WRITE entities — reference data like countries, currencies, product categories.
Caveats: (1) Stale data: if another service updates the DB directly (bypassing Hibernate), L2 cache won't know. (2) Invalidation complexity in distributed systems — you need a distributed cache (Hazelcast, Redis via JCache). (3) Not suitable for entities that change frequently — the invalidation overhead outweighs the cache benefit. (4) L2 cache is per-entity, not per-query — for query caching, use @QueryHint(name = "org.hibernate.cacheable", value = "true").