Facade Pattern

A single entry point hiding a complex subsystem. The architectural cost of skipping it.

Pattern Foundational
8 min read
pattern structural facade subsystem api-design

What it is#

The Facade pattern provides a unified, higher-level interface to a set of interfaces in a subsystem, making the subsystem easier to use. It is the architectural equivalent of the front desk at a hotel: a single counter that hides the housekeeping, kitchen, valet, billing, and concierge departments behind one greeting.

The pattern has two roles. The Facade is the small class that the client talks to — typically one method per common workflow. The Subsystem is the cluster of classes that actually do the work; they may know nothing about the facade. The client depends only on the facade. The subsystem stays internally complex, but that complexity stops at the facade boundary.

The crucial property: a facade does not stop callers from talking to the subsystem directly. It is a convenience layer for the common path, not a security wall. If a power user needs a subsystem class the facade does not expose, they reach past it. The pattern’s value is in making the common path easy, not in forbidding the uncommon one.

Class structure#

┌──────────────────────┐
Client ───►│ Facade │
├──────────────────────┤
│ doSomeWork() │
│ doAnotherJob() │
└──┬─────┬─────┬───────┘
│ │ │
▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────┐
│ Service│ │ Service│ │ Service│
│ A │ │ B │ │ C │
└────┬───┘ └────┬───┘ └────┬───┘
│ │ │
└──────────┴──────────┘
(subsystem internals,
may call each other)

The facade depends on the subsystem; the subsystem does not depend on the facade. Reversing those arrows defeats the pattern — if the subsystem calls the facade, the boundary is a cycle, not a layer.

When to use it#

Reach for Facade when all of these hold:

  • A subsystem is genuinely complex — many classes, many ordering rules, many ways to get it wrong.
  • Most callers need only a small, predictable slice of the subsystem.
  • Callers should not have to learn the internal vocabulary just to do the common thing.
  • You can leave the subsystem internally rich — power users can still reach in.

Typical scenarios:

  • Service layers in business applications. OrderService.placeOrder(...) hides the inventory check, the payment authorisation, the warehouse-pick allocation, the email send, and the analytics event behind one method.
  • SDKs over messy APIs. A vendor exposes 40 endpoints with weird auth; the SDK presents client.uploadFile(path) and handles the multipart, the chunking, the retry, the polling.
  • Bootstrap and configuration. A WebServer.start(int port) facade over ServerSocketFactory + ThreadPool + RequestRouter + SessionStore.
  • Test harnesses. TestApp.placeOrder(...) hides 200 lines of fixture setup behind one call.

When not to use it:

  • When the “subsystem” is one class. You do not need a facade for Calendar.now().
  • When every caller needs different slices, with little overlap. A facade that fronts everything is no facade at all — it is a god class.
  • When the facade would have to leak internal types in its signature (placeOrder(InternalOrderRequestBuilder b)). At that point the boundary is fiction.

How it works#

A home-theatre example, the standard interview prompt. Watching a movie touches the projector, screen, lights, surround amplifier, streaming box, and popcorn maker. Without a facade, the client memorises the order; with one, the client calls watchMovie("Inception").

// --- Subsystem: many classes the client should not have to coordinate ---
public final class Projector {
public void on() { System.out.println("projector on"); }
public void off() { System.out.println("projector off"); }
public void wideScreenMode() { System.out.println("projector: widescreen"); }
}
public final class Screen {
public void down() { System.out.println("screen down"); }
public void up() { System.out.println("screen up"); }
}
public final class Lights {
public void dim(int level) { System.out.println("lights at " + level + "%"); }
public void on() { System.out.println("lights on"); }
}
public final class Amplifier {
public void on() { System.out.println("amplifier on"); }
public void off() { System.out.println("amplifier off"); }
public void setVolume(int v) { System.out.println("volume " + v); }
public void surroundSound() { System.out.println("surround mode"); }
}
public final class StreamingBox {
public void on() { System.out.println("streaming box on"); }
public void off() { System.out.println("streaming box off"); }
public void play(String title) { System.out.println("playing: " + title); }
public void stop() { System.out.println("playback stopped"); }
}
// --- Facade: one class, one well-named method per workflow ---
public final class HomeTheatreFacade {
private final Projector projector;
private final Screen screen;
private final Lights lights;
private final Amplifier amp;
private final StreamingBox stream;
public HomeTheatreFacade(Projector projector, Screen screen, Lights lights,
Amplifier amp, StreamingBox stream) {
this.projector = projector;
this.screen = screen;
this.lights = lights;
this.amp = amp;
this.stream = stream;
}
public void watchMovie(String title) {
lights.dim(10);
screen.down();
projector.on();
projector.wideScreenMode();
amp.on();
amp.surroundSound();
amp.setVolume(8);
stream.on();
stream.play(title);
}
public void endMovie() {
stream.stop();
stream.off();
amp.off();
projector.off();
screen.up();
lights.on();
}
}
// Client:
// HomeTheatreFacade theatre = new HomeTheatreFacade(...);
// theatre.watchMovie("Inception");
// theatre.endMovie();

A few non-obvious points:

  • The facade owns the ordering. Lights dim before the projector starts; amplifier comes up before the stream plays. That ordering is the value the facade adds; capturing it once is the whole point.
  • The facade does not own the subsystem objects. They are injected. The same Projector may be controlled by the theatre facade today and a maintenance facade tomorrow — the subsystem stays independent.
  • The facade is itself injectable. Clients depend on a facade interface (or the concrete facade), not on the subsystem. Swapping in a mock facade in tests is one constructor argument.
  • Methods are workflow-shaped, not entity-shaped. watchMovie and endMovie, not getProjector and getAmplifier. A facade that exposes the subsystem one accessor at a time is hiding nothing.
  • No business decisions live here. The facade orchestrates; it does not decide which movie. The day it picks the movie, you have a use-case object, not a facade.

Variants#

VariantMechanismWhen it fits
Stateless facadeMethods only orchestrate; no fields beyond injected collaborators.The default. Easy to reason about, trivially thread-safe.
Stateful / session facadeFacade caches results, reuses connections, or holds a “current user” context.Long-lived sessions where re-fetching is expensive (Java EE SessionBean was this).
Multiple specialised facadesOne subsystem, several facades — CustomerOrderFacade, AdminOrderFacade, BatchOrderFacade.A single facade would have grown to 40 methods; segregate by caller role.
Static facadeAll methods static; subsystem classes are singletons.Utility-shaped subsystems (java.lang.Math over the FPU). Skip when you want test seams.

The “multiple specialised facades” variant is the answer to the god-class drift that single facades fall into. When the facade grows past a dozen methods, split it along usage axes — one per persona, one per workflow family — and let the subsystem stay shared.

Example systems#

The pattern shows up everywhere a complex thing wants to feel simple:

  • java.net.URLnew URL("https://example.com").openStream() hides DNS resolution, socket setup, TLS handshake, HTTP request building, redirect following, and stream wrapping. The five subsystem classes are still there if you need them.
  • JDBC DriverManager.getConnection(url, user, pw) — facades the driver-loading, URL-parsing, and authentication dance behind one call.
  • javax.faces.context.FacesContext — JSF’s facade over request, response, session, application, and view state.
  • Spring’s JdbcTemplate — facades Connection, PreparedStatement, ResultSet, transaction handling, exception translation behind template.query(sql, args, mapper).
  • Operating-system shells. cp src dst is a facade over open, read, write, close, fstat, mkdir, plus permission and copy-on-write logic.
  • Application service layers. A REST controller calling orderService.placeOrder(...) is using a facade over the domain — the controller does not know which repositories, queues, or external APIs the placement touches.

Trade-offs#

What you gain:

  • A small, learnable API on top of a large, expressive one. New team members can do useful work after reading one class instead of twenty.
  • Decoupling at the boundary. Clients depend on the facade; the subsystem can be refactored freely as long as the facade signature holds.
  • A natural place for cross-cutting concerns. Logging, metrics, transactions, retry — bolted to the facade once, not sprinkled through every call site.
  • An obvious seam for testing. Mock the facade for controller tests; mock the subsystem for facade tests.

What you pay:

  • Drift toward a god class. Every new “common workflow” gets a method on the facade. After two years, the facade has 60 methods and is the most-edited file in the repo. Specialised facades are the mitigation.
  • A wall that hides intent. If the facade’s only contribution is renaming methods, readers must hop through it to understand what actually happens. Facades earn their keep only when they encode real ordering, retries, or fan-out.
  • Two places to keep in sync. Subsystem capabilities are exposed twice — once natively, once through the facade. If the facade falls behind, callers route around it, and the facade rots.
  • Performance overhead. Usually negligible (one extra method call), but a facade that adds reflection, AOP, or proxy layers in the hot path can show up in profiles.
  • Adapter Pattern — Adapter changes one interface to another (one-to-one); Facade simplifies many interfaces into one (many-to-one). Both wrap, but the intent differs.
  • Decorator Pattern — Decorator keeps the interface and adds behaviour to one object; Facade introduces a new interface over many objects.
  • Dependency Inversion Principle (DIP) — a facade interface, with the concrete facade injected, is the high-level abstraction that DIP wants client code to depend on.
  • Interface Segregation Principle (ISP) — when one facade grows two distinct audiences, ISP pushes you toward two specialised facades over the same subsystem.
  • Library Management System — the case study uses a service-layer facade over checkout, reservation, fine-calculation, and inventory subsystems.
Search ESC

Keyboard shortcuts

Shortcuts are disabled while typing in inputs.