skip to content

How does Decorator differ from Proxy and Adapter? Use JDK examples to distinguish them.

level: seniorimportance: should knowfreq 60%

answer

  1. Same structure, different intent
  2. Decorator: same type + adds behavior + stackable
  3. Adapter: type changes (Arrays.asList, InputStreamReader)
  4. Proxy: same type + controls/defers access (reflect.Proxy)
  5. Decision test: type changed? added? gated?

basics

~20 s

All three wrap an object, but with different intent. Decorator adds behavior and keeps the same interface (BufferedInputStream over InputStream). Adapter changes the interface to a different one (Arrays.asList: array to List). Proxy controls access without changing behavior (java.lang.reflect.Proxy, lazy/remote/security gates).

solid answer

~50 s

Structurally these three patterns look alike — a wrapper holding a reference to a wrappee — so they're distinguished by intent. Decorator keeps the *same* interface and *adds* behavior, designed for stacking: BufferedInputStream and DataInputStream over an InputStream. Adapter *changes* the interface, converting one type into another the client expects: Arrays.asList adapts an array to a List, and InputStreamReader adapts a byte InputStream to a character Reader. Proxy keeps the same interface but *controls access* to the real subject rather than enriching it — lazy initialization, access checks, remoting, caching; the JDK's java.lang.reflect.Proxy plus InvocationHandler is the dynamic-proxy mechanism behind many frameworks. A quick test: if the wrapper exposes a different type, it's an Adapter; if it exposes the same type and adds functionality you'd stack, it's a Decorator; if it exposes the same type but gates or defers access to the real object, it's a Proxy.

go deeper

for a junior

Can say all three wrap an object; may only confidently nail Decorator (adds behavior, same type, e.g. BufferedInputStream).

for a middle

Distinguishes the three by intent and gives one JDK example each (Decorator=io streams, Adapter=Arrays.asList, Proxy=reflect.Proxy).

for a senior

Applies the decision test crisply, handles the subtle Decorator-vs-Proxy line, and correctly classifies tricky cases like InputStreamReader.

for a principal

Discusses why intent-based classification matters for API design, where the boundaries blur, and how dynamic proxies underpin AOP/ORM/mocking infrastructure.

## They share a structure, they differ in intent Decorator, Proxy, and Adapter are all **wrapper** patterns: an object holds a reference to another object and forwards calls to it. Because the UML is nearly identical, interviewers probe whether you understand the *intent* that separates them. ### Decorator — same interface, adds behavior - **Intent**: dynamically attach new responsibilities; designed to be *stacked*. - **Interface**: identical to the wrappee, so the decorator is substitutable and chainable. - **JDK example**: `BufferedInputStream`/`DataInputStream`/`GZIPInputStream` over `InputStream`; `Collections.synchronizedList`/`unmodifiableList` over `List`. - **Tell**: you can wrap it in another wrapper of the same family and the type stays the same. ### Adapter — different interface, converts - **Intent**: make an existing class work with a client that expects a *different* interface; a retrofit, not a planned-up-front addition. - **Interface**: the wrapper exposes a **different** type than the wrappee. - **JDK examples**: `Arrays.asList(T[])` adapts an array to the `List` interface; `InputStreamReader` adapts a byte-oriented `InputStream` to the character-oriented `Reader` interface (it also does charset decoding). - **Tell**: the input type and output type differ. You don't stack adapters of the same family to add features; you cross a type boundary once. ### Proxy — same interface, controls access - **Intent**: provide a stand-in that *controls access* to the real subject — to defer creation (virtual/lazy proxy), enforce permissions (protection proxy), call across a network (remote proxy), or cache. - **Interface**: identical to the subject (so the client can't tell), but the wrapper's job is gatekeeping, not enrichment. - **JDK example**: `java.lang.reflect.Proxy.newProxyInstance(...)` with an `InvocationHandler` builds a dynamic proxy at runtime that routes every interface call through the handler — the backbone of AOP, lazy ORM loading, RMI stubs, mocking frameworks. - **Tell**: same type as the real thing, but it decides *whether/when* to delegate rather than *adding* user-visible behavior. ## The decision test 1. Does the wrapper expose a **different** type than what it wraps? -> **Adapter**. 2. Same type, and it **adds** behavior you'd want to stack? -> **Decorator**. 3. Same type, and it **controls/defers** access to the real object (lazy, security, remote, cache)? -> **Proxy**. ## Nuance: the boundaries blur The distinction is about intent, and real code can straddle: `Collections.synchronizedList` adds locking (decorator-ish) but you could frame locking as access control (proxy-ish). The pragmatic answer in an interview is to (a) name the structural similarity, (b) state the intent that the JDK author had, and (c) give the canonical example. Adapter is the easiest to separate because the type changes; Decorator vs Proxy is the subtler pair, separated by 'enriches behavior' vs 'gates access'. ## Bonus: Decorator vs plain subclassing Decorator is often contrasted with inheritance: subclassing fixes the feature set at compile time and multiplies classes for each combination, while a decorator composes features at runtime. That's why java.io chose decorators rather than a `BufferedDataGzipFileInputStream` class for every mix.

  • Is InputStreamReader a Decorator or an Adapter, and why?
    An Adapter: it converts a byte-oriented InputStream into a character-oriented Reader (different interface) and performs charset decoding. A Decorator would keep the InputStream type and merely add behavior.
  • Give a JDK Proxy example and what it controls.
    java.lang.reflect.Proxy with an InvocationHandler creates a dynamic proxy implementing given interfaces; every call is routed through the handler. Frameworks use it for lazy loading, transactions/AOP, security checks, and remoting — controlling whether/when the real call happens.

saying these in an interview costs you the question

  • Saying Decorator and Adapter are interchangeable — Adapter changes the interface, Decorator keeps it
  • Claiming Proxy adds new user-facing behavior — its job is access control/deferral
  • Calling InputStreamReader a Decorator — it crosses byte->char, so it's an Adapter
  • Distinguishing the three purely by class diagram (they look the same; intent separates them)

context