Facade Pattern Interview Questions | JiQuest

add

#

Facade Pattern

Java design pattern deep dive

Facade Pattern in Java: 100 interview questions with professional answers.

Learn how to hide complex subsystems behind a single simplified interface, when a Facade is the right tool versus Adapter, Mediator, Proxy, or plain dependency injection, and how to keep a Facade from turning into a God Object in real Spring and microservices codebases.

100Questions
4+Related patterns compared
3Anti-patterns flagged
Client VideoConversionFacade CodecSelectorsubsystem AudioExtractorsubsystem ContainerMuxersubsystem one call, three subsystems coordinated

What makes a good Facade Pattern answer?

Interviewers want more than "it wraps a subsystem." They are listening for when a Facade is the right shape, how it stays thin, how it fails safely, and how it differs from Adapter, Proxy, and Mediator.

Real simplificationThe Facade must reduce the number of decisions a caller makes, not just forward calls 1:1.
Unified errorsSubsystem-specific exceptions get translated into one coherent exception hierarchy.
Stable contractThe public method signatures stay stable even as internal subsystems change or get replaced.
Bounded scopeA Facade should stay focused on one cohesive subsystem area, not become a catch-all entry point.
Multiplesubsystems to call? Add Facadesimplify entry point One mismatchedinterface? Use Adapter Objects talkto each other? Mediator Control accessto one object? Proxy Pick theright one
Related patternCore intentHow it differs from Facade
AdapterConverts one incompatible interface into another expected interface.Adapter wraps one object to match a contract; Facade wraps many objects to simplify a workflow.
MediatorCentralizes communication so peer objects stop referencing each other directly.Mediator coordinates two-way interaction between colleagues; Facade offers a one-way simplified entry point for clients.
ProxyControls access to a single object (lazy loading, security, remote calls).Proxy has the same interface as the real subject; Facade defines a new, simpler interface over several subjects.
Gateway (DDD)Domain-layer abstraction over an external system or infrastructure concern.Conceptually close to Facade; the distinction is mostly vocabulary and where it sits relative to the domain model.

Topics

Intent and video example Q1Facade vs utility class Q2Payment gateway Q3 Facade vs Adapter Q4Report generation Q5God Object risk Q6 Microservices client Q7Home theater example Q8Facade vs Mediator Q9 Unit testing Facades Q10Hiding N+1 queries Q11File processing Q12 DTO vs domain object Q13Evolving the interface Q14Facade with Builder Q15 Swallowed exceptions Q16Thread-safety concerns Q17Facade vs DI in Spring Q18 Legacy billing wrapper Q19Leaking exception types Q20AWS SDK workflow Q21 Sequential vs async calls Q22Facade vs Gateway Q23Search aggregation Q24 Anemic facade Q25Isolating a 3rd-party lib Q26Notification fallback tests Q27 Module boundaries Q28Facade as Spring bean Q29Lost information edge case Q30 Facade vs fluent API Q31Image processing Q32Too many methods Q33 Versioning a Facade Q34False decoupling Q35Database migration Q36 Facade vs subsystem class Q37@Transactional pitfalls Q38Facade vs Proxy Q39 Strangler Fig rollout Q40Cache consistency bug Q41Logging and tracing Q42 When Facade is unnecessary Q43Shipping rate aggregator Q44Swiss Army Knife facade Q45 Feature flags inside facade Q46Facade vs events Q47Authentication facade Q48 Static utility mistake Q49Consolidating duplication Q50Graceful degradation Q51 Batch processing job Q52Coverage false security Q53java.nio wrapper Q54 Checked vs unchecked Q55Recommendation blending Q56Spotting facade need Q57 Two facades vs one Q58Resource lifecycle Q59Kafka publishing facade Q60 Integration vs unit tests Q61Facade vs Command Q62Tax calculation facade Q63 Business logic creep Q64Constructor overload cleanup Q65Partial subsystem upgrades Q66 Sequential latency fix Q67Facade and SRP Q68Report export formats Q69 Executor API wrapper Q70Documenting the contract Q71Spring service vs POJO Q72 Geolocation fallback Q73Facade bypass erosion Q74Mapping performance cost Q75 Multi-ORM facade Q76Cross-cutting concerns Q77Weather aggregation Q78 App layer vs domain layer Q79Refactoring a switch Q80Versioned payment APIs Q81 Obscuring real work Q82Hexagonal architecture role Q83Inventory reservation Q84 Benchmarking overhead Q85Testing error translation Q86Facade vs DI composition Q87 Apache POI wrapper Q88Fragmented per-call facades Q89Multi-tenant config Q90 Bounded context / ACL Q91Printing facade Q92Async over blocking mix Q93 Coupled data model Q94Unifying logging frameworks Q95Fraud check facade Q96 Splitting a facade method Q97Reflection API wrapper Q98Maintenance cost metrics Q99 Idempotent retries Q100

Interview questions and answers

Each answer gives the design direction, the trade-off worth naming out loud, and the production concern that separates a textbook answer from a professional one.

1. Explain the intent of the Facade design pattern and design a Java example where a VideoConversionFacade hides the complexity of codec selection, audio extraction, and container muxing subsystems.

The Facade pattern provides a single, simplified interface over a set of complex subsystem classes, reducing the number of decisions and calls a client must make. The client depends only on the Facade, not on the individual subsystem contracts, which lowers coupling and makes the subsystem easier to evolve internally.

public class VideoConversionFacade {
    private final CodecSelector codecSelector = new CodecSelector();
    private final AudioExtractor audioExtractor = new AudioExtractor();
    private final ContainerMuxer muxer = new ContainerMuxer();

    public File convert(File source, String targetFormat) {
        Codec codec = codecSelector.selectFor(targetFormat);
        AudioTrack audio = audioExtractor.extract(source);
        VideoTrack video = codecSelector.transcode(source, codec);
        return muxer.mux(video, audio, targetFormat);
    }
}

// client code
File output = new VideoConversionFacade().convert(uploaded, "mp4");

Notice the client never touches Codec, AudioTrack, or muxing details; it just asks for a conversion by target format.

2. What is the structural difference between the Facade pattern and simply adding a utility class with static helper methods, and when does that distinction actually matter in a Java codebase?

A static utility class is stateless and procedural: it groups functions but has no identity, cannot be mocked polymorphically, and cannot hold subsystem dependencies as injected collaborators. A Facade is an object with a constructor that wires up real dependencies, so it can be substituted, mocked, or configured differently per environment.

The distinction matters once you need dependency injection, per-instance configuration (different credentials, different regions), or unit testing with mocked subsystems — static methods make all three painful or impossible without bytecode manipulation tools like Mockito's static mocking.

StatelessnessTestabilityDependency injection

3. Walk through how a PaymentGatewayFacade in Java would coordinate fraud checking, currency conversion, and three different third-party payment SDKs behind a single charge(Order order) method.

The Facade first runs the order through a FraudCheckService, converts the order's currency to the merchant's settlement currency via a CurrencyConverter, then picks the correct SDK adapter (Stripe, Adyen, or PayPal) based on the customer's preferred method, each already wrapped behind a common internal PaymentProcessor interface.

public class PaymentGatewayFacade {
    private final FraudCheckService fraudCheck;
    private final CurrencyConverter converter;
    private final Map<PaymentMethod, PaymentProcessor> processors;

    public ChargeResult charge(Order order) {
        FraudVerdict verdict = fraudCheck.evaluate(order);
        if (verdict.isRejected()) {
            return ChargeResult.declined(verdict.reason());
        }
        Money settled = converter.convert(order.total(), order.currency(), Currency.USD);
        PaymentProcessor processor = processors.get(order.paymentMethod());
        return processor.charge(order.customerId(), settled);
    }
}
Design note Each SDK is adapted individually (Adapter pattern) so the Facade only ever talks to the common PaymentProcessor interface, cleanly separating "adapt one SDK" from "orchestrate the workflow."

4. How does the Facade pattern differ from the Adapter pattern in terms of intent, and can you show a scenario where you would need both in the same Java module?

Adapter's intent is interface translation: it makes one existing class's interface match what a client expects, without changing the number of participants. Facade's intent is simplification: it reduces the surface area a client interacts with across multiple subsystem classes, regardless of whether their interfaces are compatible.

In the payment example above, three SDKs each get an Adapter to normalize them into a common PaymentProcessor interface, and the PaymentGatewayFacade then orchestrates fraud checking, conversion, and the adapted processors as one simplified workflow — Adapter solves shape mismatch, Facade solves workflow complexity.

5. Describe how you would design a ReportGenerationFacade that internally uses a template engine, a PDF renderer, and an email dispatcher, while keeping each subsystem independently testable.

Each subsystem — template engine, PDF renderer, email dispatcher — should be injected through its own interface (TemplateEngine, PdfRenderer, EmailDispatcher), never instantiated directly inside the Facade. This lets you unit test each subsystem in isolation and unit test the Facade's orchestration logic with all three mocked out.

public class ReportGenerationFacade {
    private final TemplateEngine templates;
    private final PdfRenderer renderer;
    private final EmailDispatcher emailer;

    public ReportGenerationFacade(TemplateEngine templates, PdfRenderer renderer, EmailDispatcher emailer) {
        this.templates = templates;
        this.renderer = renderer;
        this.emailer = emailer;
    }

    public void generateAndSend(ReportRequest request) {
        String html = templates.render(request.templateName(), request.data());
        byte[] pdf = renderer.toPdf(html);
        emailer.sendWithAttachment(request.recipient(), "Your report", pdf);
    }
}
Constructor injectionInterface boundariesIsolated tests

6. What are the risks of a Facade class accumulating so many responsibilities that it becomes a God Object, and how would you detect this happening in a Java codebase over time?

As new features get bolted on, teams often add "just one more method" to an existing Facade because it is already the known entry point, until it accumulates unrelated responsibilities: reporting, notifications, billing, and search all in one class. This breaks the Single Responsibility Principle, makes the class hard to test in isolation, and forces unrelated changes to recompile and redeploy together.

Detection signals include a constructor with more than six or seven injected dependencies, a class file that keeps growing across unrelated pull requests, and code review comments repeatedly asking "why does this class also know about X?" Static analysis tools that flag high fan-out (number of classes a class depends on) are useful early-warning signals.

Warning sign If you cannot describe a Facade's purpose in one sentence without using "and," it is likely accumulating unrelated responsibilities.

7. In a microservices client library, how would you use the Facade pattern to present a single OrderServiceClient interface over multiple internal REST clients and retry/circuit-breaker logic?

The OrderServiceClient Facade would internally hold a low-level HTTP client (say, a Feign or RestClient instance), a retry policy (Resilience4j), and a circuit breaker, and expose only domain-meaningful methods like placeOrder(OrderRequest) or getOrderStatus(String orderId). Consumers never see HTTP status codes, retry counts, or circuit-breaker state directly.

public class OrderServiceClient {
    private final RestClient http;
    private final RetryPolicy retryPolicy;
    private final CircuitBreaker breaker;

    public OrderStatus getOrderStatus(String orderId) {
        return breaker.executeSupplier(() ->
            retryPolicy.execute(() -> http.get("/orders/" + orderId, OrderStatus.class)));
    }
}

This keeps resilience concerns centralized in one place so every call site automatically benefits from consistent retry and fallback behavior.

8. Show how a HomeTheaterFacade in Java would simplify turning on a projector, sound system, and streaming device, and explain what happens when one subsystem component throws an exception mid-sequence.

The classic textbook example composes several device classes and exposes one watchMovie() method that sequences their individual calls. The important design question is what happens on partial failure: if the projector powers on but the sound system throws an exception, do you leave the projector on, or roll everything back?

public class HomeTheaterFacade {
    private final Projector projector;
    private final SoundSystem soundSystem;
    private final StreamingDevice streamingDevice;

    public void watchMovie(String title) {
        try {
            projector.on();
            soundSystem.on();
            streamingDevice.play(title);
        } catch (DeviceException ex) {
            shutdownAll();
            throw new HomeTheaterException("Failed to start movie: " + title, ex);
        }
    }

    private void shutdownAll() {
        projector.off();
        soundSystem.off();
        streamingDevice.stop();
    }
}
Watch out Without an explicit rollback/cleanup step, a mid-sequence failure leaves devices in an inconsistent state that confuses the next call.

9. What is the difference between a Facade and a Mediator pattern, given that both seem to 'coordinate' multiple objects, and how would misapplying one for the other cause design problems?

Facade provides a one-directional simplified entry point: clients call the Facade, and the Facade calls subsystem objects, but subsystem objects generally do not need to know about each other. Mediator centralizes many-to-many communication between peer "colleague" objects that would otherwise reference each other directly, such as UI widgets that need to react to each other's state changes.

Misapplying Facade where Mediator is needed leads to subsystem objects still holding direct references to each other for the two-way chatter, so the Facade only handles the "front door" calls while a tangled web of internal references remains — you get the illusion of decoupling without the substance. Conversely, forcing a Mediator onto a simple one-way workflow adds unnecessary indirection and event-routing complexity.

10. How would you unit test a Facade class in Java when its constructor wires up five real subsystem dependencies — what mocking strategy keeps the test meaningful without becoming an integration test?

Inject all five dependencies as interfaces through the constructor, then use Mockito to create mocks or stubs for each in the unit test, verifying that the Facade calls them in the correct order with the correct arguments and correctly translates their return values or exceptions. The test should assert on orchestration logic — sequencing, conditionals, error mapping — not on what the mocked subsystems themselves do.

@Test
void chargeDeclinesWhenFraudCheckRejects() {
    when(fraudCheck.evaluate(order)).thenReturn(FraudVerdict.rejected("velocity"));
    ChargeResult result = facade.charge(order);
    assertThat(result.isDeclined()).isTrue();
    verifyNoInteractions(converter, processors.get(order.paymentMethod()));
}

Keep a small separate suite of integration tests (with test containers or sandbox SDKs) to validate that the real subsystems behave as the mocks assumed.

11. Explain a scenario where introducing a Facade over a legacy JDBC-based DAO layer could hide N+1 query problems from callers, and how you would prevent that performance issue from becoming invisible.

If a CustomerOrderFacade.getCustomerWithOrders(id) method internally calls one query to fetch the customer and then loops over each order calling a separate DAO method to fetch line items, callers see one clean method call while the database silently executes dozens of queries. The simplification of the interface can mask an O(n) query pattern that only shows up under load.

Prevent this by adding query-count assertions or execution-time budgets in integration tests, exposing metrics (query count per Facade call) via micrometer, and preferring batch-fetch DAO methods (findLineItemsByOrderIds(List)) inside the Facade rather than per-row calls.

N+1 queriesBatch fetchingQuery-count metrics

12. Design a FileProcessingFacade in Java that wraps virus scanning, format validation, and compression, and explain how you would handle partial failures across those steps.

Model each step's outcome explicitly rather than throwing on the first failure, so the Facade can report exactly which stage failed. A sealed result type communicates success or the specific failure stage back to the caller without leaking scanner- or compressor-specific exception types.

public sealed interface ProcessingResult permits Processed, Rejected {}
public record Processed(File output) implements ProcessingResult {}
public record Rejected(Stage stage, String reason) implements ProcessingResult {}

public class FileProcessingFacade {
    public ProcessingResult process(File input) {
        if (!virusScanner.isClean(input)) {
            return new Rejected(Stage.VIRUS_SCAN, "malware detected");
        }
        if (!formatValidator.isValid(input)) {
            return new Rejected(Stage.VALIDATION, "unsupported format");
        }
        return new Processed(compressor.compress(input));
    }
}

13. What are the trade-offs of exposing a Facade's return type as a rich domain object versus a simple DTO, particularly regarding coupling between client code and subsystem internals?

Returning a rich domain object (say, the subsystem's own OrderEntity) is convenient short-term but couples every caller to that subsystem's internal shape — if you swap the ORM or restructure the entity, every client breaks. Returning a purpose-built DTO owned by the Facade's own package decouples callers from subsystem internals at the cost of an extra mapping step on every call.

The professional default is a Facade-owned DTO or record for any Facade meant to be a stable public boundary (library, cross-module API), and only relaxing that to direct domain objects when the Facade and its callers live in the same tightly-coupled module and change together.

14. How would you evolve a Facade interface over time in a Java library without breaking existing consumers, when the underlying subsystems gain new required parameters?

Add new overloaded methods or a request object with sensible defaults rather than changing existing method signatures. When a subsystem gains a genuinely required new parameter, introduce a new method (or a builder-style request object) and mark the old overload @Deprecated with a clear migration note, rather than breaking binary compatibility.

@Deprecated(since = "2.3", forRemoval = true)
public ChargeResult charge(Order order) {
    return charge(new ChargeRequest(order, ChargeOptions.defaults()));
}

public ChargeResult charge(ChargeRequest request) { /* new required options */ }
Backward compatibilityOverloadingDeprecation path

15. Explain how the Facade pattern can be combined with the Builder pattern to simplify construction of a complex EmailNotificationFacade that needs optional attachments, templates, and locales.

Facade simplifies the runtime call, but when the request itself has many optional fields, a plain method with seven parameters is unreadable. Pairing Facade with Builder lets the client construct a readable request object, then hand it to one Facade method.

EmailRequest request = EmailRequest.builder()
    .to("user@example.com")
    .template("order-confirmation")
    .locale(Locale.US)
    .attachment(invoicePdf)
    .build();

notificationFacade.send(request);

The Facade stays a single, stable method (send(EmailRequest)), while the Builder absorbs the complexity of optional parameters, avoiding telescoping constructors.

16. Describe a production incident where a poorly designed Facade swallowed exceptions from subsystem calls, and how you would redesign the error-handling contract to avoid silent failures.

A common real incident: a NotificationFacade.notify(user, message) method wrapped its email and SMS calls in a try/catch that logged at debug level and returned normally on any exception, so when the SMS provider's API key expired, thousands of notifications silently failed for two weeks with no alerts, and nobody noticed until customers complained.

The fix is to make the failure mode explicit in the return type or to rethrow a translated exception rather than swallowing it — never catch a broad exception purely to suppress it. Return a result object indicating partial success (email sent, SMS failed) and wire failure counts into metrics and alerting, so degraded delivery is visible immediately rather than discovered by customer complaints.

Anti-pattern A catch block that only logs at debug level and swallows the exception is functionally equivalent to deleting the error.

17. How does thread-safety concern differ between a stateless Facade and a Facade that caches subsystem results, and what Java concurrency primitives would you use for the latter?

A stateless Facade that only holds references to its (thread-safe) subsystem collaborators needs no special synchronization — each call is independent. Once a Facade introduces mutable state, such as an in-memory cache of subsystem results, it must guard that state against concurrent reads and writes, or callers on different threads can see stale or torn data.

public class PricingFacade {
    private final ConcurrentMap<String, CachedPrice> cache = new ConcurrentHashMap<>();

    public Price getPrice(String sku) {
        CachedPrice cached = cache.get(sku);
        if (cached != null && !cached.isExpired()) {
            return cached.price();
        }
        Price fresh = pricingEngine.compute(sku);
        cache.put(sku, new CachedPrice(fresh, Instant.now().plus(Duration.ofMinutes(5))));
        return fresh;
    }
}

ConcurrentHashMap with computeIfAbsent, an AtomicReference for whole-snapshot swaps, or a dedicated cache library like Caffeine are the standard choices, depending on whether you need per-key or whole-cache atomicity.

18. Compare using a Facade pattern versus directly injecting all subsystem interfaces via constructor injection in a Spring service — what does the Facade actually buy you in that context?

If a single service class is the only consumer of a set of subsystem dependencies, injecting them directly is fine and adding a Facade in between is pure ceremony. The Facade earns its place when multiple consumers (several controllers, a scheduled job, and a message listener) all need the same orchestration sequence — without it, that sequencing logic gets duplicated or drifts between call sites.

The Facade also gives you one place to change the orchestration (add a new fraud check step, add caching) without touching every consumer, and one seam for testing that sequence independently of any particular controller.

19. Design a LegacyBillingFacade that wraps a 15-year-old mainframe integration and a modern billing microservice simultaneously, and explain how you would phase out the legacy system without changing the Facade's public contract.

The Facade's public method signature (generateInvoice(AccountId id)) stays fixed while its implementation routes to either backend based on a migration flag, typically keyed by account or by percentage rollout. This lets you dual-run, compare outputs, and gradually cut over without any caller code changes.

public class LegacyBillingFacade {
    public Invoice generateInvoice(AccountId id) {
        if (migrationFlags.usesModernBilling(id)) {
            return modernBillingClient.generateInvoice(id);
        }
        return mainframeAdapter.generateInvoice(id);
    }
}
Feature flag rolloutStrangler FigStable contract

20. What common mistake do developers make when they let a Facade leak subsystem-specific exception types (e.g., SQLException) instead of translating them into a unified exception hierarchy?

Letting a checked SQLException or a vendor-specific SDK exception escape the Facade forces every caller to know about and handle a technology detail the Facade was supposed to hide, and it breaks the moment you swap the underlying subsystem for a different technology. It also usually means the caller has to add an import for a library it otherwise never touches.

public Order findOrder(String id) {
    try {
        return dao.findById(id);
    } catch (SQLException ex) {
        throw new OrderLookupException("Unable to load order " + id, ex);
    }
}
Fix Always catch subsystem-specific exceptions at the Facade boundary and rethrow a domain-meaningful, technology-agnostic exception, preserving the original as the cause.

21. How would you apply the Facade pattern to simplify a multi-step AWS SDK workflow (S3 upload, SNS notification, DynamoDB record creation) into a single AssetUploadFacade method?

The Facade hides three distinct AWS SDK clients (S3, SNS, DynamoDB) and the ordering/consistency concerns between them behind one uploadAsset(AssetUploadRequest) method, so calling code never touches PutObjectRequest, PublishRequest, or PutItemRequest directly.

public class AssetUploadFacade {
    public AssetRecord uploadAsset(AssetUploadRequest request) {
        String key = s3Client.putObject(request.bucket(), request.fileName(), request.content());
        AssetRecord record = new AssetRecord(UUID.randomUUID().toString(), key, Instant.now());
        dynamoDbClient.putItem(toItem(record));
        snsClient.publish(topicArn, "AssetUploaded: " + record.id());
        return record;
    }
}
Design note Write the DynamoDB record before publishing the SNS notification, so downstream subscribers reading the record after the notification never see a missing row.

22. Explain the performance implications of a Facade method that synchronously calls four downstream services in sequence versus parallelizing those calls with CompletableFuture in Java.

Sequential calls accumulate latency additively — four calls at 100ms each cost roughly 400ms total — while independent calls issued concurrently via CompletableFuture cost roughly as long as the slowest single call, since they overlap in time. The trade-off is added complexity: you need a shared executor with a bounded thread pool, careful exception aggregation, and a timeout strategy so one slow call doesn't stall the whole composition indefinitely.

public DashboardData loadDashboard(String userId) {
    CompletableFuture<Profile> profile = CompletableFuture.supplyAsync(() -> profileService.get(userId), pool);
    CompletableFuture<List<Order>> orders = CompletableFuture.supplyAsync(() -> orderService.recentFor(userId), pool);
    CompletableFuture<List<Recommendation>> recs = CompletableFuture.supplyAsync(() -> recEngine.forUser(userId), pool);

    return CompletableFuture.allOf(profile, orders, recs)
        .thenApply(v -> new DashboardData(profile.join(), orders.join(), recs.join()))
        .join();
}

23. What is the difference between the Facade pattern and the Gateway pattern often used in Domain-Driven Design, and when would you prefer one term/structure over the other?

Structurally, Gateway and Facade are nearly identical — both present a simplified interface hiding infrastructure complexity. The distinction is mostly about placement and vocabulary: Gateway is typically used specifically at the boundary between the domain layer and an external system or infrastructure concern (a payment gateway, a repository gateway), emphasizing that it belongs to the domain's port/adapter boundary, while Facade is the more general GoF term used for simplifying any complex subsystem, domain-related or not.

Prefer "Gateway" in a DDD-flavored codebase where you already use terms like Aggregate, Repository, and Bounded Context, so the naming stays consistent with the rest of the domain vocabulary; use "Facade" for general-purpose subsystem simplification outside a strict DDD context.

24. Design a SearchFacade that unifies results from Elasticsearch, a SQL database, and a cache layer, and explain how you would rank and merge heterogeneous result sets behind that single interface.

The Facade queries the cache first for hot results, falls through to Elasticsearch for full-text relevance-ranked matches, and supplements with exact-match SQL lookups (for example, an exact SKU or order number), then merges all three into one ranked list using a common SearchResult shape with a normalized relevance score.

public List<SearchResult> search(String query) {
    List<SearchResult> cached = cacheLayer.lookup(query);
    if (!cached.isEmpty()) return cached;

    List<SearchResult> textMatches = elasticClient.search(query);
    List<SearchResult> exactMatches = sqlDao.findExactMatches(query);
    List<SearchResult> merged = resultMerger.mergeAndRank(textMatches, exactMatches);
    cacheLayer.store(query, merged);
    return merged;
}

Keep the ranking/merging logic in its own ResultMerger class rather than inline in the Facade, so it can be tuned and tested independently of the data-source calls.

25. How would you detect and refactor an 'anemic facade' — one that just forwards every call 1:1 to a single subsystem class without adding real simplification value?

An anemic Facade is recognizable when every public method is a one-line pass-through to a single injected dependency with no added logic, error translation, or orchestration across multiple subsystems — it exists in name only. This adds an indirection layer that increases the number of classes to navigate without any corresponding benefit.

// anemic — adds nothing
public class UserFacade {
    private final UserRepository repo;
    public User findById(String id) { return repo.findById(id); }
}

The fix is usually to delete the Facade and let callers depend on the subsystem interface directly (if there truly is only one subsystem and no orchestration), or to give the Facade actual responsibility — validation, caching, aggregation across more than one subsystem — that justifies its existence.

Needless indirectionDelete or add value

26. Explain how you would use the Facade pattern to isolate a Java application from a third-party library that you plan to replace in six months, and what interface design decisions minimize future churn.

Define a small, application-owned interface expressed entirely in your domain's vocabulary (for example, DocumentStorage with store/retrieve/delete), then implement it with a Facade class whose only job is translating those calls into the specific third-party library's API. All application code depends on DocumentStorage, never on the library's own types.

To minimize future churn, keep the interface's method signatures free of any type that originates from the third-party library — no library-specific request/response objects, exceptions, or enums should cross the boundary. When you swap libraries later, only the Facade implementation changes; the interface and every caller stay untouched.

Anti-corruption boundaryVendor isolation

27. What testing strategy would you use to verify that a NotificationFacade correctly falls back from push notification to SMS to email when higher-priority channels fail?

Unit test each fallback branch independently with mocked channel clients: push succeeds (no fallback attempted), push fails and SMS succeeds (verify SMS was called and email was not), and all three fail (verify the Facade surfaces a clear "all channels failed" result rather than throwing an unclear exception).

@Test
void fallsBackToSmsWhenPushFails() {
    when(pushClient.send(any())).thenThrow(new PushDeliveryException("token expired"));
    when(smsClient.send(any())).thenReturn(DeliveryResult.success());

    DeliveryResult result = facade.notify(user, message);

    verify(smsClient).send(any());
    verifyNoInteractions(emailClient);
    assertThat(result.channelUsed()).isEqualTo(Channel.SMS);
}

Add a small integration test against sandboxed provider endpoints to confirm the mocked failure modes (timeouts, error codes) match what the real providers actually return.

28. Describe how a Facade class in a multi-module Maven/Gradle project should be positioned architecturally to enforce module boundaries and prevent other modules from bypassing it.

Place the Facade interface in the module's public API package and keep the subsystem classes it wraps package-private or in an internal sub-package that is not exported. In Gradle, this can be enforced with Java Module System (module-info.java exports statements) or with build-tool conventions like Gradle's api vs implementation configurations, so consuming modules cannot even compile against internal classes.

module com.example.billing {
    exports com.example.billing.facade;
    // com.example.billing.internal is not exported — unreachable from outside
}
module-info.javaPackage-private internalsEncapsulation

29. How does the Facade pattern interact with dependency injection frameworks like Spring — should the Facade itself be a singleton bean, and what issues arise if its subsystem dependencies have different scopes?

A stateless Facade is naturally a good fit for Spring's default singleton scope, since it holds only references to other beans and no per-request mutable state. Problems arise when the Facade (singleton) is injected with a subsystem bean scoped as request or prototype — Spring cannot inject a shorter-lived bean directly into a longer-lived singleton without a scoped proxy, because the singleton is created once while the shorter-lived bean needs a fresh instance per request.

@Service
public class ReportFacade {
    private final ObjectProvider<RequestScopedContext> contextProvider;

    public ReportFacade(ObjectProvider<RequestScopedContext> contextProvider) {
        this.contextProvider = contextProvider;
    }

    public Report generate() {
        RequestScopedContext context = contextProvider.getObject();
        // ...
    }
}

Use ObjectProvider/Provider or a scoped proxy (proxyMode = ScopedProxyMode.TARGET_CLASS) to bridge the scope mismatch safely.

30. Explain a real-world edge case where a Facade's simplified method signature loses information the caller actually needed, forcing an awkward workaround or interface redesign.

A common case: a checkout(cart) Facade method that returns a plain boolean for success/failure hides why it failed (out of stock, payment declined, address invalid), forcing callers who need to show a specific error message to the user to poll subsystem state directly, defeating the Facade's purpose and reintroducing the coupling it was meant to remove.

The fix is to design the return type up front to carry the information callers realistically need — a result type with a reason code or a sealed set of outcome subtypes — rather than collapsing rich subsystem outcomes into a boolean or void, since retrofitting richer return types later is a breaking change for every caller.

Design lesson Model failure reasons in the return type from day one; boolean and void returns age poorly on orchestration methods.

31. Compare the Facade pattern to simply using a Fluent API/method chaining to simplify a complex subsystem — what problems does Facade solve that fluent interfaces don't?

A fluent API simplifies construction or configuration of a single object through chained method calls, but it does not by itself coordinate multiple independent subsystems or translate errors across them — it is a syntax-level convenience, closer in spirit to Builder. Facade solves a structural problem: reducing the number of distinct subsystem classes a client must know about and orchestrating calls across them, including error handling and sequencing.

In practice, a Facade's individual methods can expose a fluent style internally (returning a request builder before executing), but the fluency is a usability layer on top of the Facade's real job of hiding subsystem complexity — they solve different problems and often combine well together.

32. Design an ImageProcessingFacade that wraps resizing, watermarking, and format conversion, and explain how you would add progress-reporting callbacks without breaking the simplicity the Facade is meant to provide.

Add an optional callback parameter (a functional interface) with a default no-op implementation via method overloading, so existing callers who don't care about progress remain unaffected while new callers can opt in.

public interface ProgressListener {
    void onStep(String stepName, int percentComplete);
}

public class ImageProcessingFacade {
    public File process(File input, ImageOptions options) {
        return process(input, options, (step, pct) -> {});
    }

    public File process(File input, ImageOptions options, ProgressListener listener) {
        listener.onStep("resize", 0);
        File resized = resizer.resize(input, options.dimensions());
        listener.onStep("watermark", 40);
        File watermarked = watermarker.apply(resized, options.watermark());
        listener.onStep("convert", 80);
        File result = converter.convert(watermarked, options.targetFormat());
        listener.onStep("done", 100);
        return result;
    }
}

33. What happens to a Facade's usefulness when the number of methods on it grows past 20-30, and what refactoring pattern (e.g., splitting into multiple Facades) would you apply?

Once a Facade accumulates dozens of methods, it stops being "simple" — callers face the same discoverability and cognitive-load problem the Facade was meant to solve, just one level higher. This usually signals the Facade is covering multiple, only loosely related use cases (say, both catalog browsing and order management) that should be separate Facades.

The fix is to split by client use case or by cohesive subsystem area — for example, splitting a bloated ShopFacade into CatalogFacade and OrderFacade — applying the Interface Segregation Principle at the Facade level, so each remains small enough to understand at a glance.

Interface segregationSplit by use case

34. How would you version a Facade interface exposed as a public API in a Java library so that internal subsystem refactors don't force downstream consumers to recompile?

Publish the Facade interface in a stable, versioned package (following semantic versioning at the artifact level), keep the interface's binary contract additive-only within a major version (new default methods, no signature changes), and isolate all internal subsystem classes in a separate, unpublished internal module so refactoring them never touches the published API's compiled bytecode.

public interface BillingFacade {
    Invoice generateInvoice(AccountId id);

    // added in 2.1 as a default method — does not break 2.0 binaries
    default Invoice generateInvoice(AccountId id, InvoiceOptions options) {
        return generateInvoice(id);
    }
}

Reserve major version bumps for genuine breaking changes to the Facade's method signatures.

35. Explain how the Facade pattern can mask tight coupling between subsystems rather than actually reducing it, and give a concrete example where this false sense of decoupling caused a maintenance problem.

A Facade only decouples clients from subsystems; it does nothing about coupling between the subsystems themselves. If InventorySubsystem and PricingSubsystem internally hold direct references to each other and share mutable state, wrapping them both in an OrderFacade gives the illusion of a clean architecture while the real coupling underneath remains just as brittle.

A concrete example: a team wrapped a tangled billing and shipping subsystem pair in a FulfillmentFacade, declared the refactor "done," and later discovered that changing the shipping subsystem's data model still broke billing at runtime, because the two had never actually been decoupled from each other — only from their callers.

Key distinction Facade reduces client-to-subsystem coupling, not subsystem-to-subsystem coupling — don't confuse the two when claiming a refactor improved the architecture.

36. Design a DatabaseMigrationFacade that coordinates schema versioning, data backfill, and rollback logic across Flyway and custom scripts, explaining the exception-safety guarantees it must provide.

The Facade should run Flyway's schema migration first, then execute custom backfill scripts within a transaction boundary where possible, and expose a single migrate() method that either fully succeeds or triggers a defined rollback path — it must never leave the schema in a state that is ahead of the data or vice versa.

public class DatabaseMigrationFacade {
    public MigrationResult migrate() {
        MigrationInfo before = flyway.info().current();
        try {
            flyway.migrate();
            backfillRunner.runPendingBackfills();
            return MigrationResult.success();
        } catch (Exception ex) {
            rollbackTo(before);
            return MigrationResult.failed(ex.getMessage());
        }
    }
}
Guarantee The Facade must document whether it provides all-or-nothing semantics or best-effort partial migration — this affects how operators respond to a failed run.

37. How would you decide whether a piece of functionality belongs inside the Facade itself versus in a dedicated subsystem class that the Facade merely delegates to?

Functionality belongs in the Facade only if it is pure orchestration — sequencing, conditional routing between subsystems, error translation, or aggregating results. Anything with its own testable business rules, non-trivial algorithms, or reusable logic that another caller might need independently of the Facade's workflow belongs in a dedicated subsystem class.

A useful heuristic: if you can imagine writing a unit test for the logic that doesn't need to mock any of the Facade's other collaborators, that logic almost certainly belongs in its own class, not inline in the Facade.

38. What are the implications of a Facade method being annotated @Transactional in a Spring application when it internally calls multiple repositories — what pitfalls around transaction propagation should you watch for?

When a Facade method is annotated @Transactional, Spring's proxy opens one transaction that spans all repository calls made directly within that method, giving atomicity across them. The common pitfall is calling @Transactional methods on this from within the same class — Spring's proxy-based AOP does not intercept self-invocation, so the inner call silently runs outside any new transaction boundary the annotation implies.

@Service
public class OrderFacade {
    @Transactional
    public void placeOrder(Order order) {
        inventoryRepository.reserve(order);
        paymentRepository.recordCharge(order);   // same transaction — correct
        auditFacade.logOrder(order);             // separate bean call — Spring proxy applies correctly
    }
}
Pitfall Also watch propagation settings: a Facade calling another @Transactional method with REQUIRES_NEW will commit that inner work even if the outer transaction later rolls back.

39. Compare the Facade pattern with the Proxy pattern when both are used to control access to a subsystem — what distinguishes 'simplifying an interface' from 'controlling access to an object'?

A Proxy implements the same interface as the object it wraps and controls access to that one object — adding lazy loading, caching, security checks, or remote-call marshaling — while remaining a drop-in substitute for the real subject. A Facade defines a new, simpler interface over potentially many different subsystem objects; it is not meant to be a substitute for any single one of them.

They can be layered together: a Facade might internally call a subsystem object through a security Proxy, so the Facade handles workflow simplification while the Proxy handles access control transparently underneath it.

Same interface vs new interfaceOne object vs many

40. Explain how you would introduce a Facade incrementally (Strangler Fig style) in front of a monolithic order-processing class without a big-bang rewrite.

Start by defining the target Facade interface based on the monolith's existing public methods, then implement it initially as a thin pass-through delegating straight to the monolith, so all callers can be migrated to depend on the new interface with zero behavior change. Once every caller goes through the Facade, incrementally extract and replace pieces of the monolith's internals behind that stable interface, one subsystem at a time.

// step 1: thin pass-through, no behavior change
public class OrderFacade {
    private final LegacyOrderProcessor legacy;
    public OrderResult placeOrder(OrderRequest request) {
        return legacy.process(request); // later: route pieces to new services
    }
}

This lets you migrate callers and internals on separate timelines, which is the core value of the Strangler Fig approach.

41. Describe a scenario where a CacheFacade wrapping Redis and an in-memory cache introduces a subtle bug because callers assume read-after-write consistency that the Facade doesn't actually guarantee.

If CacheFacade.put(key, value) writes to Redis asynchronously (fire-and-forget) for throughput, a caller that immediately calls get(key) on a different application instance may read a stale value from its local in-memory layer or an empty result from Redis before the write propagates, even though the write call already returned.

This is dangerous precisely because the Facade's simple put/get signature looks like it should behave like a local, strongly consistent map — the fix is to document the actual consistency guarantee explicitly (eventual vs read-after-write) and, if callers genuinely need read-after-write, offer a distinct synchronous write method or a write-through mode rather than leaving the assumption implicit.

Hidden contract A simple method signature can imply stronger guarantees than the implementation actually provides — document consistency semantics explicitly.

42. How would you design logging and observability (metrics, tracing spans) inside a Facade so that operators can still diagnose which underlying subsystem is slow or failing?

Wrap each subsystem call inside the Facade with its own timed span and metric tag identifying the subsystem, rather than emitting one aggregate timing for the whole Facade method — otherwise a slowdown in one of four subsystem calls is invisible in the aggregate number.

public OrderResult placeOrder(OrderRequest request) {
    Timer.Sample sample = Timer.start(meterRegistry);
    try (var span = tracer.spanBuilder("facade.placeOrder").startScopedSpan()) {
        reserve(request);   // each internal method has its own child span/timer
        charge(request);
        notify(request);
        return OrderResult.success();
    } finally {
        sample.stop(meterRegistry.timer("order.facade.total"));
    }
}

Propagate a correlation/trace ID through every subsystem call so a single request can be followed end-to-end across logs from different subsystems.

43. What is the difference between a Facade and simply exposing a well-designed public API on the subsystem classes themselves — when is adding a separate Facade class actually unnecessary?

If there is genuinely only one subsystem class involved and its own public API is already simple and cohesive, wrapping it in a Facade adds an indirection layer with no real benefit — you'd just be renaming the same methods on a new class. Facade earns its keep specifically when multiple subsystem classes need to be coordinated, or when a single subsystem's native API is too low-level or awkward for the majority of callers' needs.

A good litmus test: if deleting the proposed Facade and having callers use the subsystem class directly would not change what any caller has to know or do, the Facade is unnecessary.

44. Design a ShippingRateFacade that queries three carrier APIs (FedEx, UPS, DHL) and returns the cheapest rate, and explain how you would handle one carrier's API timing out without blocking the whole response.

Issue all three carrier calls concurrently with individual timeouts, and treat a timeout or error from any single carrier as "no rate available from that carrier" rather than failing the whole request — the Facade should return the best rate among whichever carriers responded in time.

public ShippingQuote getCheapestRate(Shipment shipment) {
    List<CompletableFuture<Optional<Rate>>> futures = carriers.stream()
        .map(carrier -> CompletableFuture.supplyAsync(() -> carrier.quote(shipment), pool)
            .orTimeout(2, TimeUnit.SECONDS)
            .exceptionally(ex -> Optional.empty()))
        .toList();

    List<Rate> rates = futures.stream()
        .map(CompletableFuture::join)
        .flatMap(Optional::stream)
        .toList();

    return rates.stream().min(Comparator.comparing(Rate::amount))
        .map(ShippingQuote::of)
        .orElseThrow(() -> new NoRatesAvailableException(shipment.id()));
}

45. Explain the anti-pattern of a 'Swiss Army Knife Facade' that tries to serve every possible client use case, and how interface segregation principles suggest splitting it instead.

A Swiss Army Knife Facade grows a method for every client's specific need until it exposes dozens of overlapping, subtly different variants (getOrder, getOrderWithItems, getOrderForAdmin, getOrderSummary), because nobody wants to create a "second Facade." This makes the class hard to learn, hard to keep consistent, and prone to accidental divergence between similar methods.

The Interface Segregation Principle suggests splitting by client role instead: a lean CustomerOrderFacade for customer-facing needs and a separate AdminOrderFacade for back-office needs, each exposing only what its specific client actually uses, even if they both delegate to some of the same underlying subsystem classes.

Interface segregationSplit by client role

46. How would you handle configuration and feature flags inside a Facade so that different callers can opt into different subsystem behaviors without exposing subsystem details directly?

Accept an options object (not raw subsystem flags) in the Facade's method signature, expressed in terms the caller understands (for example, ShippingOptions.expedited()), and translate that into the specific subsystem configuration internally — the caller should never need to know which underlying carrier or code path a flag maps to.

public ShippingQuote getRate(Shipment shipment, ShippingOptions options) {
    if (options.isExpedited()) {
        return expeditedCarrier.quote(shipment);
    }
    return standardCarrier.quote(shipment);
}

Keep feature-flag evaluation (from a flag service like LaunchDarkly) inside the Facade implementation too, so toggling a rollout percentage never requires a caller-side code change.

47. Compare using a Facade pattern versus an event-driven architecture (publishing domain events) to decouple a client from multiple downstream actions — what are the consistency trade-offs?

A Facade performs its downstream actions synchronously (or at least within one orchestrated call), giving the caller an immediate, strongly consistent view of success or failure for every action it triggered. An event-driven approach instead publishes one event and lets independent subscribers react asynchronously, which decouples deployment and scaling of the downstream actions but introduces eventual consistency — the caller cannot know synchronously whether every downstream reaction succeeded.

Choose Facade when the caller genuinely needs a consistent, immediate outcome (e.g., "was the payment charged"), and events when downstream actions are independent side effects that can tolerate delay and partial, retryable failure (e.g., "send a welcome email," "update analytics").

48. Design an AuthenticationFacade that unifies OAuth2, SAML, and API-key based authentication mechanisms behind one authenticate(request) method, and explain how you'd structure the result type to represent differing claim sets.

Each mechanism produces a different shape of identity information (OAuth2 gives scopes and a subject claim, SAML gives an assertion with attributes, API keys give just a caller identifier), so the result type needs a common core (principal ID, authentication method used, issued-at time) plus an extensible claims map for mechanism-specific data, rather than trying to force every mechanism into identical fields.

public record AuthResult(String principalId, AuthMethod method, Instant issuedAt, Map<String, Object> claims) {}

public class AuthenticationFacade {
    public AuthResult authenticate(AuthRequest request) {
        return switch (request.type()) {
            case OAUTH2 -> oauth2Provider.authenticate(request);
            case SAML -> samlProvider.authenticate(request);
            case API_KEY -> apiKeyProvider.authenticate(request);
        };
    }
}

49. What common mistake occurs when a Facade is implemented as a static utility class with static methods instead of an injectable instance, and how does that hurt testability in Java?

Static methods bind the Facade's subsystem dependencies to static fields or hidden singletons, so tests cannot substitute mocks through normal constructor injection — the only workarounds are heavyweight static mocking frameworks (like Mockito's mockStatic) or reflection hacks, both of which are brittle and signal a design smell rather than a clean testing seam.

// hard to test
public class PaymentFacadeUtil {
    public static ChargeResult charge(Order order) {
        return StripeClient.getInstance().charge(order); // hidden singleton dependency
    }
}

Making the Facade an injectable instance with constructor-provided dependencies restores normal mock substitution and lets each test run in isolation without global state leaking between tests.

50. Explain how you would refactor duplicated subsystem-orchestration logic scattered across multiple controller classes into a single reusable Facade, and what risks that consolidation introduces.

Identify the common sequence (say, three controllers all validate an order, reserve inventory, and charge payment with slightly different orderings or error handling), extract the canonical correct sequence into a new Facade method, and replace each controller's inline logic with a call to that shared method — then delete the duplicated code.

The main risk is that the duplicated copies were not actually identical — subtle differences in validation order or error handling across the controllers may have been intentional for their specific use case, and blindly consolidating them into one shared method can silently change behavior for some callers. Audit each duplicate carefully and, if real differences exist, parameterize the Facade method rather than forcing a single behavior on all callers.

Risk Consolidation can silently erase intentional per-caller differences that looked like accidental duplication but weren't.

51. How would you design a Facade so it degrades gracefully (partial results) when one of its three underlying subsystems is temporarily unavailable, versus a Facade that fails fast on any subsystem error?

Graceful degradation requires each subsystem call to be wrapped independently so a failure in one does not prevent the others from contributing to the result, and the return type must be able to represent "partial" as a distinct, visible state rather than silently omitting data. Fail-fast is appropriate when a partial result would be actively misleading or unsafe to act on, such as a financial balance calculation where an incomplete number is worse than an explicit error.

public DashboardData load(String userId) {
    Optional<Profile> profile = safely(() -> profileService.get(userId));
    Optional<List<Order>> orders = safely(() -> orderService.recentFor(userId));
    return new DashboardData(profile, orders, profile.isEmpty() || orders.isEmpty());
}

Document which mode a given Facade method uses so callers know whether to check a "degraded" flag or treat any exception as total failure.

52. Describe how you would apply the Facade pattern in a Java batch-processing job that must coordinate reading from a file, validating rows, transforming data, and writing to a database in fixed-size chunks.

A BatchImportFacade hides the chunking loop, the file reader, the row validator, and the database writer behind a single importFile(Path file) method, tracking counts of processed, skipped, and failed rows internally so the job's calling code (a scheduler or CLI entry point) doesn't need to know about chunk sizes or writer batching.

public ImportSummary importFile(Path file) {
    ImportSummary summary = new ImportSummary();
    try (RowReader reader = readerFactory.open(file)) {
        List<Row> chunk = new ArrayList<>(CHUNK_SIZE);
        for (Row row : reader) {
            if (validator.isValid(row)) chunk.add(row); else summary.skip(row);
            if (chunk.size() == CHUNK_SIZE) { writer.writeBatch(transformer.transform(chunk)); summary.processed(chunk.size()); chunk.clear(); }
        }
        if (!chunk.isEmpty()) { writer.writeBatch(transformer.transform(chunk)); summary.processed(chunk.size()); }
    }
    return summary;
}

53. What is the effect of a Facade pattern on unit test coverage metrics when most business logic actually lives in the subsystems it wraps — how do you avoid a false sense of security from testing only the Facade?

Testing only the Facade with mocked subsystems gives you high coverage numbers on the Facade class itself while leaving the actual business logic in each subsystem entirely untested — the coverage report looks healthy, but the risk is concentrated exactly where the tests don't reach. Coverage percentage on the Facade tells you nothing about correctness of the subsystems it delegates to.

Track coverage per subsystem class independently, not just on the Facade, and make sure code review explicitly checks that new business logic added "temporarily" inside the Facade for convenience gets its own subsystem-level unit tests rather than being exercised only indirectly through Facade-level tests with mocks.

False security High coverage on a thin orchestration class says nothing about the correctness of the logic it delegates to.

54. Explain how you would use the Facade pattern to provide a simplified interface over Java's java.nio file APIs for a team that mostly needs basic read/write/copy operations.

java.nio.file is powerful but verbose for common cases — dealing with Path, Files, StandardOpenOption, and checked IOException for every call. A FileOpsFacade exposes three or four intention-revealing methods that internally choose sensible defaults (UTF-8 encoding, overwrite behavior, atomic move where supported).

public class FileOpsFacade {
    public String readText(String path) {
        try {
            return Files.readString(Path.of(path), StandardCharsets.UTF_8);
        } catch (IOException ex) {
            throw new FileOpsException("Failed to read " + path, ex);
        }
    }

    public void writeText(String path, String content) {
        try {
            Files.writeString(Path.of(path), content, StandardCharsets.UTF_8,
                StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
        } catch (IOException ex) {
            throw new FileOpsException("Failed to write " + path, ex);
        }
    }
}

55. Compare a Facade class exposing checked exceptions versus unchecked exceptions in Java, and discuss how that choice affects the ergonomics of client code calling into a complex subsystem.

Checked exceptions on a Facade force every caller to either handle or explicitly declare the failure, which can be valuable when the failure is a routine, expected outcome the caller should actively decide how to handle (like a validation failure). Unchecked exceptions keep call sites clean for failures that are exceptional and usually only handled at a high level (a global exception handler or a boundary layer), avoiding boilerplate try/catch scattered through business logic that can't meaningfully recover anyway.

The professional default for most Facades is unchecked, domain-specific exceptions (a custom RuntimeException subclass) combined with clear documentation of what can be thrown, reserving checked exceptions for the rare case where forcing explicit handling at every call site genuinely improves correctness.

56. Design a RecommendationFacade that blends collaborative filtering, content-based filtering, and business rule overrides, and explain how you would make the blending strategy swappable without changing the Facade's public method signature.

Inject a BlendingStrategy interface into the Facade rather than hardcoding the blending logic, so the strategy implementation (weighted average, rule-first override, A/B-tested variant) can be swapped via configuration or dependency injection without touching the Facade's method signature — this is the Strategy pattern used inside a Facade.

public class RecommendationFacade {
    private final BlendingStrategy blendingStrategy;

    public List<Recommendation> recommend(String userId) {
        List<Recommendation> collaborative = collaborativeEngine.recommend(userId);
        List<Recommendation> contentBased = contentEngine.recommend(userId);
        List<Recommendation> blended = blendingStrategy.blend(collaborative, contentBased);
        return ruleEngine.applyOverrides(userId, blended);
    }
}

57. How would you identify, during a code review, that a class calling five different subsystem interfaces directly should instead be refactored to depend on a Facade?

Warning signs during review include a constructor injecting five or more unrelated dependencies into a single controller or service, repeated sequences of calls across those dependencies that look similar to sequences in other classes, and business logic that seems to be "orchestration" rather than that class's actual core responsibility (a controller doing payment-and-inventory-and-notification coordination instead of just handling the HTTP concern).

A practical heuristic: if you would need to change three or more of those dependencies' call sites together whenever the workflow changes, that is a strong signal the orchestration belongs in a Facade rather than scattered across whichever class happened to need it first.

58. Explain a scenario where two different client modules need genuinely different simplified views over the same subsystems, and why creating two separate Facades is better than one configurable Facade.

A customer-facing storefront module needs an OrderFacade that hides payment failure details behind a generic "please try again" message, while an internal operations dashboard module needs full error detail, refund capability, and manual override methods over the same order and payment subsystems. Cramming both needs into one configurable Facade (with a "mode" flag) creates a class whose behavior depends on hidden state and whose interface tries to serve two audiences with conflicting needs.

Two separate Facades — StorefrontOrderFacade and OperationsOrderFacade — each depending on the same underlying subsystem classes but exposing different, purpose-built methods, keeps each interface focused and avoids one client accidentally calling a method meant only for the other's use case.

Role-specific facadesNo mode flags

59. What are the memory and object-lifecycle implications of a Facade that holds references to expensive subsystem resources (e.g., connection pools, thread pools) — how should its lifecycle be managed in a Java application?

If a Facade owns expensive resources directly (rather than receiving already-managed ones through injection), it must implement proper startup and shutdown hooks — otherwise thread pools and connection pools leak on redeploy or application context refresh, and repeated instantiation of the Facade (for example, accidentally creating a new instance per request) can exhaust system resources quickly.

@Service
public class ReportFacade implements DisposableBean {
    private final ExecutorService executor = Executors.newFixedThreadPool(4);

    @Override
    public void destroy() {
        executor.shutdown();
    }
}

In Spring, prefer letting the framework manage such resources as their own beans with defined lifecycle callbacks (@PreDestroy), and inject them into the Facade rather than having the Facade construct and own them directly.

60. Describe how the Facade pattern applies to wrapping a complex Kafka producer/consumer setup (serialization, partitioning strategy, retry policy) into a single EventPublishingFacade.

The Facade hides KafkaProducer configuration, the chosen Serializer, the partitioning key strategy, and retry/backoff settings behind a single publish(DomainEvent event) method, so application code never constructs a ProducerRecord or thinks about topic names and partition keys directly.

public class EventPublishingFacade {
    public void publish(DomainEvent event) {
        String topic = topicResolver.resolve(event.getClass());
        String key = partitionKeyStrategy.keyFor(event);
        ProducerRecord<String, byte[]> record =
            new ProducerRecord<>(topic, key, serializer.serialize(event));
        producer.send(record, (metadata, ex) -> {
            if (ex != null) errorHandler.handle(event, ex);
        });
    }
}
SerializationPartition key strategyAsync send callback

61. How would you write integration tests that validate a Facade correctly orchestrates real subsystems in the right order, distinct from unit tests that mock those subsystems out?

Integration tests should spin up real (or realistically simulated) subsystem instances — a real embedded database, a Testcontainers-managed Kafka broker, a WireMock stub server for third-party HTTP APIs — and assert on the final observable state after calling the Facade, rather than verifying internal call order (which is a unit-test concern).

@Test
void placeOrderPersistsInventoryAndPublishesEvent() {
    facade.placeOrder(sampleOrder);

    assertThat(inventoryRepository.findById(sku).quantity()).isEqualTo(9);
    assertThat(kafkaConsumer.pollForRecord(Duration.ofSeconds(5))).isNotNull();
}

Unit tests verify "did the Facade call things in the right order with the right arguments" using mocks; integration tests verify "did the real system end up in the right state," each catching different classes of bugs.

62. Compare the Facade pattern to the Command pattern when both are used to encapsulate a complex operation — what does Command offer (undo, queuing, logging) that a plain Facade method does not?

A Facade method executes its orchestration immediately and returns a result — it is not itself a first-class object that can be stored, queued, retried later, or undone. The Command pattern encapsulates a request as an object with its own execute() (and often undo()) method, which can be placed in a queue, logged for replay, scheduled, or composed into macro-commands.

They combine naturally: a Facade's method can internally construct and execute Command objects for each step, gaining undo/redo or audit logging for free, while callers still enjoy the Facade's simple entry point without needing to know Commands are involved underneath.

63. Design a TaxCalculationFacade that must call different regional tax engines based on jurisdiction, and explain how you would keep the Facade's method signature stable as new jurisdictions are added.

Keep the public method signature jurisdiction-agnostic (calculateTax(TaxableTransaction transaction)), and resolve the correct regional engine internally through a registry keyed by jurisdiction code, so adding a new country's tax engine means registering a new implementation, not changing the Facade's interface or any caller's code.

public class TaxCalculationFacade {
    private final Map<String, TaxEngine> enginesByJurisdiction;

    public TaxResult calculateTax(TaxableTransaction transaction) {
        TaxEngine engine = enginesByJurisdiction.getOrDefault(
            transaction.jurisdictionCode(), defaultEngine);
        return engine.calculate(transaction);
    }
}
Registry lookupOpen/closed principle

64. What mistake do teams make when they put orchestration logic that has real business value (e.g., discount stacking rules) inside a Facade, blurring the line between infrastructure simplification and business logic?

Discount stacking rules — which promotions can combine, in what order, with what caps — are genuine business logic with its own edge cases and requirements churn, not plumbing. Burying that logic inline inside a CheckoutFacade makes it hard to unit test independently, hard for a business analyst or QA to reason about in isolation, and easy to accidentally couple to unrelated orchestration changes.

The fix is to extract a dedicated DiscountStackingPolicy (or similar) class that owns the business rules and can be tested and versioned on its own, with the Facade simply calling it as one step in its orchestration — keeping "how do we combine subsystem calls" separate from "what are the actual business rules."

Blurred line If a rule has its own edge cases that product or business stakeholders care about, it does not belong inline in a Facade.

65. How would you refactor a Facade that has accumulated multiple constructor overloads and optional parameters over time, and what pattern would you introduce to clean that up?

Multiple constructor overloads usually mean the Facade's construction-time configuration has grown organically (a timeout here, a feature flag there) without a coherent design. Introduce a dedicated configuration object (or a Builder for the Facade itself) that collects all the optional settings, and reduce the Facade to a single constructor taking that configuration plus its required subsystem dependencies.

public class ReportFacade {
    public ReportFacade(TemplateEngine templates, PdfRenderer renderer,
                         EmailDispatcher emailer, ReportFacadeConfig config) {
        // one constructor; config.timeout(), config.retries(), etc. replace overloads
    }
}
Configuration objectTelescoping constructor fix

66. Explain how you would design a Facade to be resilient to partial subsystem upgrades in a Java monorepo, where one subsystem's API changes but others haven't been updated yet.

Keep the Facade's dependency on each subsystem behind a stable internal interface, and version subsystem implementations independently behind that interface, so upgrading one subsystem's concrete implementation doesn't require the Facade or its other subsystem dependencies to change at all. Use adapter classes at each subsystem boundary if the new subsystem version's API shape differs from the old one.

In practice this means the Facade should depend on abstractions it owns (or that live in a shared, stable contract module), never directly on a specific subsystem module's concrete classes, so a rolling upgrade across a monorepo can happen module by module without breaking compilation or runtime behavior elsewhere.

67. Describe a production scenario where a Facade over multiple external HTTP APIs caused cascading latency because it called them sequentially, and how you fixed it using async composition.

A ProductDetailFacade called an inventory API, a pricing API, and a reviews API one after another, each taking around 150ms; under normal conditions the page loaded in about 450ms, but during a pricing API slowdown to 2 seconds, every single product page request also took over 2 seconds, because the sequential design meant total latency was the sum of all three, gated by the slowest one at the end of the chain.

public ProductDetail getDetail(String sku) {
    CompletableFuture<Inventory> inventory = CompletableFuture.supplyAsync(() -> inventoryApi.get(sku), pool);
    CompletableFuture<Price> price = CompletableFuture.supplyAsync(() -> pricingApi.get(sku), pool)
        .completeOnTimeout(Price.unavailable(), 500, TimeUnit.MILLISECONDS);
    CompletableFuture<List<Review>> reviews = CompletableFuture.supplyAsync(() -> reviewsApi.get(sku), pool);

    return new ProductDetail(inventory.join(), price.join(), reviews.join());
}

Running the calls concurrently with a bounded timeout on the slowest one capped worst-case latency at roughly the timeout value instead of the actual slow-API response time.

68. What is the relationship between the Facade pattern and the Single Responsibility Principle — does a Facade violate SRP by definition, or does it actually help enforce it at a higher level?

A well-designed Facade does not violate SRP because its one responsibility is "provide a simplified entry point for this specific workflow" — orchestration is itself a legitimate, singular responsibility, distinct from the responsibilities of the subsystems it coordinates. It actually helps enforce SRP elsewhere by pulling orchestration logic out of controllers, jobs, and other classes that would otherwise take on "do the business logic and also coordinate five subsystems" as two responsibilities in one class.

A Facade only starts violating SRP when it accumulates unrelated orchestration responsibilities (billing orchestration and search orchestration in the same class) — at that point the fix is to split it, not to abandon the pattern.

69. Design a ReportExportFacade that supports exporting to CSV, Excel, and PDF, and explain the trade-off between adding a format parameter to one Facade method versus exposing three separate methods.

A single export(ReportData data, ExportFormat format) method keeps the public surface small and makes it trivial to add a new format later without adding a new method, but it pushes a runtime branch (or a registry lookup) inside the implementation and requires callers to pass a format value that could be invalid. Three separate methods (exportCsv, exportExcel, exportPdf) are more discoverable via IDE autocomplete and let each method have format-specific parameters, at the cost of a slightly larger interface that grows with every new format.

public byte[] export(ReportData data, ExportFormat format) {
    Exporter exporter = exportersByFormat.get(format);
    return exporter.export(data);
}

Prefer the single parameterized method when formats share the same input shape and are likely to keep growing; prefer separate methods when each format genuinely needs different parameters.

70. How would you use the Facade pattern to isolate application code from Java's verbose java.util.concurrent executor and future APIs when submitting and tracking background jobs?

A BackgroundJobFacade hides ExecutorService creation, Future/CompletableFuture handling, and exception unwrapping (ExecutionException) behind intention-revealing methods like runAsync(Runnable job) and submit(Supplier<T> job), so application code never touches raw executor or future APIs directly.

public class BackgroundJobFacade {
    private final ExecutorService executor = Executors.newFixedThreadPool(8);

    public <T> JobHandle<T> submit(Supplier<T> job) {
        CompletableFuture<T> future = CompletableFuture.supplyAsync(job, executor)
            .exceptionally(ex -> { throw new JobFailedException(ex.getCause()); });
        return new JobHandle<>(future);
    }
}

71. Explain how you would document a Facade's contract (preconditions, side effects, failure modes) so that new team members don't need to read the underlying subsystem code to use it safely.

Document, directly in the Javadoc on each public method, the preconditions (what must be true of the input), every side effect (what gets written, published, or charged), the specific exceptions that can be thrown and under what conditions, and any consistency guarantees (synchronous vs eventual) — treating the Facade's Javadoc as the actual API contract, not just a syntax reminder.

/**
 * Charges the customer for the given order and reserves inventory.
 * Side effects: creates a payment record, decrements inventory, publishes an OrderPlaced event.
 * Throws PaymentDeclinedException if the charge is rejected (no inventory change occurs).
 * Throws InventoryUnavailableException if stock check fails (no charge occurs).
 */
public OrderResult placeOrder(OrderRequest request) { ... }
Contract-first JavadocSide-effect documentation

72. Compare a Facade implemented as a Spring @Service versus one implemented as a plain POJO instantiated manually — what does the framework integration change about testing and dependency wiring?

A Spring-managed @Service Facade gets its dependencies auto-wired by the container, participates in Spring's lifecycle (proxying for @Transactional, AOP advice, scoped beans), and can be tested with @SpringBootTest or sliced context tests that load real wiring. A plain POJO built with new Facade(dep1, dep2) has no framework overhead, wires up in milliseconds for pure unit tests, and works identically outside any framework, but loses automatic AOP features like declarative transactions unless you wire them manually.

Prefer the plain POJO approach for fast, framework-independent unit tests of orchestration logic, and reserve full Spring context tests for verifying that the framework wiring (transactions, security, scopes) behaves correctly around the Facade.

73. Describe how you would design a GeolocationFacade that falls back from GPS to IP-based geolocation to a user-provided address, and how the Facade should communicate confidence level in its result.

Each geolocation source has a different accuracy — GPS is precise but may be unavailable indoors, IP geolocation is coarse (city-level), and a user-provided address is exact but requires user input — so the result type should carry an explicit confidence/precision indicator rather than returning a bare coordinate that looks equally trustworthy regardless of source.

public record LocationResult(double lat, double lon, LocationSource source, Precision precision) {}

public class GeolocationFacade {
    public LocationResult resolve(LocationRequest request) {
        return gpsProvider.tryLocate(request)
            .or(() -> ipProvider.tryLocate(request))
            .or(() -> addressProvider.tryLocate(request))
            .orElseThrow(() -> new LocationUnavailableException(request.userId()));
    }
}

74. What common mistake leads to a Facade being bypassed by other developers who call the subsystem classes directly because the Facade doesn't expose a needed capability — how do you prevent that erosion?

When a developer needs a capability the Facade doesn't expose — say, a bulk variant of an operation the Facade only offers one-at-a-time — the path of least resistance is often to import the subsystem class directly and bypass the Facade "just this once," and that exception becomes a habit that other developers copy, eroding the boundary the Facade was meant to enforce.

Prevent this by treating a missing capability as a signal to extend the Facade (add the bulk method) rather than working around it, enforcing the boundary at compile time (package-private subsystem classes, module boundaries) so bypassing isn't even possible, and reviewing pull requests for direct subsystem imports outside the Facade's own package.

Erosion pattern One "just this once" bypass normalizes the next one — enforce the boundary structurally, not just by convention.

75. Explain the performance cost of a Facade that performs its own data transformation/mapping (e.g., DTO conversion) on every call, and when you would introduce caching versus optimizing the mapping itself.

Mapping cost (reflection-based mappers like older MapStruct configurations, or manual field-by-field copying across many nested objects) is usually small per call but can add up under high throughput, especially with naive reflection-based mapping libraries repeatedly inspecting class metadata. Profile before optimizing — the mapping step is rarely the actual bottleneck compared to network or database calls in the same method.

Cache the mapped result only when the same input is requested repeatedly and the underlying source data doesn't change often (reference/lookup data); when the mapping itself is measured to be genuinely slow, prefer switching to a compile-time code-generating mapper (MapStruct's annotation-processor mode) over caching, since caching adds invalidation complexity that a faster mapper avoids entirely.

No comments
Leave a Comment