A Facade provides one simple entry point in front of a tangled subsystem. It's appropriate when callers don't need (and shouldn't depend on) the internals; it slides into anti-pattern territory when it becomes the only way to use the subsystem and starts accumulating logic that doesn't belong there.
When it belongs
The classic case: a third-party library or legacy module exposes ten classes with subtle ordering requirements, and 90% of callers do the same five-call sequence. Wrap that sequence behind one method.
// Subsystem: subtitles, audio, video, codec, projector...
public class HomeTheaterFacade {
private final Amp amp;
private final Player player;
private final Projector projector;
private final Lights lights;
public HomeTheaterFacade(Amp a, Player p, Projector pr, Lights l) {
this.amp = a; this.player = p; this.projector = pr; this.lights = l;
}
public void watchMovie(String title) {
lights.dim(10);
projector.on();
amp.on();
amp.setVolume(5);
player.on();
player.play(title);
}
public void endMovie() {
player.stop();
amp.off();
projector.off();
lights.dim(100);
}
}
The caller writes theater.watchMovie("Dune") and never thinks about the projector. But the subsystem classes remain public — power users can still skip the facade and call the parts directly.
Real-world facades in the JDK and Spring
javax.faces.context.FacesContext— facade over the entire JSF request lifecycle.org.springframework.jdbc.core.JdbcTemplate— facade overDataSource,Connection,Statement,ResultSet, exception translation.java.util.logging.Logger— facade over handlers, formatters, filters.
Each one buys callers a 10x simpler API without taking away the underlying types when somebody actually needs them.
When it becomes an anti-pattern: the "God Facade"
The facade hardens into an anti-pattern when:
- It absorbs business logic. A
UserFacadethat started as a thin wrapper grows authorization checks, validation, persistence orchestration, email sending, and metrics — turning into the application's entire service layer with the wrong name. - Callers can't bypass it. Subsystem classes are made package-private to "force" everyone through the facade, and now legitimate edge cases need ugly workarounds.
- It's the only API. Every change to any subsystem class also changes the facade signature, and you end up with a facade that has 80 methods and 4,000 lines.
The smell: a single class that any team can plausibly say they "depend on" because everything funnels through it.
The refactor when a facade has grown too large
Split by use case, not by subsystem. A HomeTheaterFacade that does watchMovie, playMusic, and videoCall is three facades fighting for one class. Extract MovieController, MusicController, CallController, each reusing the same subsystem objects.