The Facade pattern puts a simple class in front of a group of complex classes, so a client calls one method instead of orchestrating five or six services in the right order. In a retail system, "place order" usually means reserving stock, calculating the price, charging payment, creating a shipment, and sending a notification, plus rollback logic for when a step fails halfway through. This article covers the Facade pattern in Java through a working order-placement example written for Java 17. It walks through the structure, error handling and rollback, and how to tell Facade apart from Adapter, Proxy, and Mediator. It also covers the cases where a Facade turns into a "god object" that you should avoid.

In this series, the patterns closest to Facade are the ones that wrap objects: Adapter pattern: connecting legacy APIs to new interfaces, Decorator pattern: lessons from java.io applied to services, and Proxy pattern: JDK dynamic proxies, CGLIB and Spring AOP. The Facade pattern also "stands in front of" other objects, but its goal is to simplify an entire subsystem, not a single object. The related design foundations are in SOLID principles in Java with practical examples, especially the Interface Segregation principle.

The problem: the client has to know too much

Suppose you have five services: InventoryService (stock), PricingService (price and shipping fee), PaymentService, ShippingService, and NotificationService. A web controller, a job that imports orders from an e-commerce marketplace, and an internal staff tool all need to "place an order". If each of them orchestrates the five services on its own, you run into three problems.

  • Duplicated logic. The call order, the way the total is calculated, and the rollback rules get copied into three places. Changing one rule means changing three places.
  • High coupling. All three depend on five concrete classes. Switching shipping providers forces changes in all three.
  • Easy to get wrong. Forgetting to release stock when a payment fails is a serious business bug, yet nothing forces the client author to remember it.

The Facade pattern gathers the orchestration into one place. The client only knows placeOrder(request).

Structure of the Facade pattern

According to refactoring.guru, the Facade pattern "provides a simplified interface to a library, a framework, or any other complex set of classes". The components are:

RoleResponsibilityIn the order example
FacadeProvides convenient access to part of the subsystem and knows which component to forward each request toOrderFacade
Additional FacadeA secondary facade that keeps one facade from growing too broadYou could split out a RefundFacade
SubsystemMany interdependent objects that do not know the facade existsInventory, pricing, payment, shipping, notification
ClientCalls the facade instead of calling the subsystem directlyController, order import job

Two characteristics of the Facade pattern matter most. First, the subsystem does not know the facade exists, so you can add a Facade pattern to a running system without changing the old classes. Second, a facade does not forbid direct access: a client that needs advanced features can still call the subsystem directly. The Facade pattern is a convenient shortcut, not a wall.

Implementing the Facade pattern: one entry point for order placement

The sub-services in the example are small in-memory classes, enough to show the orchestration flow. Inventory can reserve and release stock, payment can reject a charge that exceeds the limit, and shipping can fail when there is no route. The code snippets in this article are a trimmed version of code/s6-09/src/FacadeDemo.java (with the System.out.println lines, the bodies of release and refund, and the declarations of the other four services left out). The full code, which was run to produce the output below, is in that file:

static class InventoryService {
    private final Map<String, Integer> stock = new HashMap<>(Map.of("SP-001", 5, "SP-002", 0));
    boolean reserve(String sku, int qty) {
        int cur = stock.getOrDefault(sku, 0);
        if (cur < qty) return false;
        stock.put(sku, cur - qty);
        System.out.println("   [inventory] reserve " + qty + " x " + sku + ", " + stock.get(sku) + " left");
        return true;
    }
    void release(String sku, int qty) { /* add the stock back */ }
}

static class PaymentService {
    private final long limit;
    PaymentService(long limit) { this.limit = limit; }
    String charge(String customer, long amount) {
        if (amount > limit) throw new IllegalStateException("card declined: " + amount + " > limit " + limit);
        return "TXN-" + customer + "-" + amount;
    }
    void refund(String txn) { /* refund the payment */ }
}

Here the Facade pattern receives the five services through its constructor (it does not new them itself, which makes them easy to replace in tests) and encapsulates the whole flow:

record OrderRequest(String customer, String sku, int qty, String city) {}
record OrderResult(boolean ok, String message, String txn, String shipment) {}

static class OrderFacade {
    // ... five services received through the constructor ...

    OrderResult placeOrder(OrderRequest r) {
        if (!inventory.reserve(r.sku(), r.qty())) {
            return new OrderResult(false, "out of stock " + r.sku(), null, null);
        }
        try {
            long amount = pricing.total(r.sku(), r.qty()) + pricing.shippingFee(r.city());
            String txn = payment.charge(r.customer(), amount);
            try {
                String shipment = shipping.createShipment(r.city(), r.sku(), r.qty());
                notification.sms(r.customer(), "Order " + r.sku() + " x" + r.qty() + " confirmed");
                return new OrderResult(true, "success", txn, shipment);
            } catch (RuntimeException e) {
                payment.refund(txn);
                throw e;
            }
        } catch (RuntimeException e) {
            inventory.release(r.sku(), r.qty());
            return new OrderResult(false, e.getMessage(), null, null);
        }
    }
}

The client no longer sees inventory, pricing, or shipping. The most valuable part is in the failure branches: the facade is the only place that knows the rollback order. The code uses only Java 17 features (records, Map.of); the command java --source 17 FacadeDemo.java runs on a newer JDK and produces the real output below. The success and out-of-stock cases:

=== 1. Order placed successfully (the client calls one method) ===
   [inventory] reserve 2 x SP-001, 3 left
   [payment] charge 260000 VND -> TXN-KH01-260000
   [shipping] create shipment VD-462-SP-001
   [notification] send to KH01: Order SP-001 x2 confirmed
OrderResult[ok=true, message=success, txn=TXN-KH01-260000, shipment=VD-462-SP-001]
=== 2. Out of stock ===
OrderResult[ok=false, message=out of stock SP-002, txn=null, shipment=null]

The total of 260,000 VND is 2 items at 120,000 each plus a 20,000 delivery fee for inner-city Hanoi. For the out-of-stock order, the facade returns a failure result immediately without touching payment. The two remaining failure scenarios show the rollback:

=== 3. Payment declined -> Facade releases the stock ===
Stock SP-001 before: 3
   [inventory] reserve 3 x SP-001, 0 left
   [inventory] release 3 x SP-001, 3 left
OrderResult[ok=false, message=card declined: 395000 > limit 300000, txn=null, shipment=null]
Stock SP-001 after: 3
=== 3b. Shipping fails -> Facade refunds, then releases the stock ===
   [inventory] reserve 1 x SP-001, 2 left
   [payment] charge 155000 VND -> TXN-KH04-155000
   [payment] refund TXN-KH04-155000
   [inventory] release 1 x SP-001, 3 left
OrderResult[ok=false, message=no delivery route to Remote Island, txn=null, shipment=null]
Stock SP-001 after: 3

In scenario 3, stock is 3 both before and after: the facade returned the items it had reserved. In scenario 3b, the failure happens at the third step, so the facade refunds the payment first and then releases the stock, which is the reverse of the order in which the steps ran. This is the part that a client orchestrating on its own can easily forget.

One honest note about the Facade pattern: this example runs in a single process, so "rollback in code" is enough. When the services are separate systems connected over a network, a rollback can itself fail, messages can be duplicated, and you need mechanisms such as idempotency keys and sagas. The Facade pattern is still a reasonable place to put the orchestration, but do not treat the catch blocks above as sufficient for a distributed environment.

Using the Facade pattern correctly

A few design decisions are worth weighing when you implement the Facade pattern:

  • Return type. The example uses OrderResult with an ok flag for expected business failures (out of stock, card declined). Unexpected failures such as a lost connection should be exceptions. Keeping the two kinds separate lets the client handle each clearly.
  • Interface or concrete class. If there are many clients or you need to fake the facade in tests, have it implement an OrderPlacing interface. Clients then depend on the interface, in the spirit of Dependency Inversion.
  • No complex business rules. A facade should orchestrate, not calculate. Fee and promotion calculations still belong in PricingService.
  • Scope by context. One facade per business context (placing orders, returns, reporting), not one facade for the whole system.

The Facade pattern in the JDK and popular libraries

You have used the Facade pattern many times without necessarily noticing:

  • SLF4J. The official manual opens with the sentence "The Simple Logging Facade for Java (SLF4J) serves as a simple facade or abstraction for various logging frameworks". Application code calls a single API, and the backend (Logback, reload4j, java.util.logging, and so on) is chosen at deployment time.
  • Utility classes such as java.nio.file.Files. A single Files.readString(path) call hides opening the channel, decoding, reading, and closing the resource. It is a convenient shortcut to a larger subsystem, in the spirit of the Facade pattern, even though the JDK documentation does not call it by that name.
  • Spring JdbcTemplate and similar Template/Client classes. They wrap obtaining a connection, creating a statement, handling exceptions, and closing resources behind a few methods. Many sources place them close to Facade in their goal of simplification, but they also apply Template Method, so this is a blurry boundary.

The common sign: a small API in front, a large subsystem behind it, and ordinary users never need to look inside.

Facade pattern vs Adapter, Proxy, and Mediator

These four patterns, including the Facade pattern, all sit between two parties, so they are easy to confuse. The table below summarizes them following refactoring.guru:

PatternHow many objects it wrapsInterfacePurpose
FacadeMany objects (a whole subsystem)A new, simpler interfaceSimplify how the subsystem is used
AdapterUsually one objectConverts to the interface the client expectsMake two mismatched interfaces work together
ProxyOne objectKeeps the same interfaceControl access
MediatorMany objectsComponents know only the mediatorCentralize communication between components

The difference between the Facade pattern and Mediator is worth remembering: a Facade adds no new functionality, and the subsystem objects can still talk to each other directly. A Mediator forces components to communicate through it, and they do not know each other. A Proxy shares its interface with the real object, so the two can be swapped; a Facade exposes a different interface. A quick way to remember it: Adapter changes the shape, Proxy keeps the shape and guards the door, and Facade collapses many doors into one.

When not to use the Facade pattern

The main drawback of the Facade pattern is stated in the pattern's own documentation: a facade can become a god object coupled to every class in the application. These are the signs that you are entering dangerous territory:

  • The facade has dozens of unrelated methods. Split it into several facades by business context (Additional Facade).
  • The Facade pattern is stuffed with business rules. When an if that calculates a promotion shows up in the facade, that logic belongs to a dedicated service.
  • Every change touches the facade. If every new feature means editing the same class, it carries too many responsibilities.
  • The subsystem is already simple. Wrapping two calls in another layer only adds a useless middle layer.
  • The facade hides errors the client needs to handle. If every failure is swallowed into ok=false, the client cannot tell user errors from system errors.

Common mistakes with the Facade pattern

  • Forgetting to roll back a step in the Facade pattern. Every step that succeeded before the failure point needs a compensating action, in reverse order. Write a test for each failure point, like scenarios 3 and 3b above.
  • The facade creates the subsystem with new. This makes it hard to replace in tests. Receive dependencies through the constructor as in the example.
  • Forbidding direct access to the subsystem when using the Facade pattern. This turns the facade into a bottleneck instead of a shortcut; let advanced features go straight through.
  • Using the facade to hold state. A facade should hold no business state; state belongs to the subsystem or the data store.
  • Confusing it with a service layer. In a Spring application, the service class usually already does the orchestration. Name something OrderFacade only when it really hides several services.

Facade pattern checklist

  1. Apply the Facade pattern only when two or more clients orchestrate the same chain of services.
  2. The facade only orchestrates and rolls back; it holds no business rules.
  3. Dependencies come in through the constructor, and clients depend on an interface when needed.
  4. Every failure point has a compensating action and its own test.
  5. One Facade pattern per business context; split it when it grows large.
  6. The subsystem stays directly accessible for advanced needs.
  7. If the subsystem runs over a network, add idempotency and a saga-style compensation mechanism.

Next article

The Facade pattern reduces many objects to a single entry point; the next pattern solves a different structural problem, which is treating a single node and a whole group of nodes the same way in one tree. To revisit the previous pattern, read Proxy pattern: JDK dynamic proxies, CGLIB and Spring AOP. The next article in the series: Composite pattern: treating object trees uniformly.