Backend for Frontend (BFF) Pattern in Microservices
The Backend for Frontend (BFF) pattern gives each type of client its own small backend. The web app talks to a Web BFF, the mobile app talks to a Mobile BFF, and each BFF calls the shared microservices and returns exactly the data its screens need. Instead of one general API that tries to suit every client, each frontend gets an API designed for it.
This tutorial explains the problem the BFF pattern solves, how it works, and how it differs from an API gateway. Three runnable Java examples show response shaping, keeping tokens out of the browser, and supporting old app versions. Interview questions follow at the end.
1. What Is the Backend for Frontend Pattern?
A BFF is a server-side component that belongs to one frontend. It is usually built and owned by the same team that builds that frontend. The pattern was described by Phil Calçado from his work at SoundCloud and made popular by Sam Newman around 2015.
A BFF does three jobs: it combines data from several services, much like the aggregator pattern; it shapes that data for one client's screens; and it handles concerns that are specific to that client, such as browser sessions or mobile app versions.
2. Why One API for All Clients Fails
Different clients need different things from the same data:
- A mobile app on a slow network wants small responses and as few calls as possible.
- A web app on a large screen shows more detail, more images and more fields.
- A smartwatch or partner integration may need only two or three fields.
With one general-purpose API, the mobile app downloads fields it never shows, makes several calls per screen, and joins data on the phone. Every new screen asks the backend team for "just one more field", and changing a response risks breaking another client. The API becomes a compromise that suits nobody, and every frontend team waits on one backend team.
3. How the BFF Pattern Works
- The client sends one request to its own BFF, for example
GET /mobile/product-page/P-501. - The BFF calls the shared services it needs, in parallel where possible.
- It removes unused fields, formats values and combines the results.
- It returns one response shaped for that screen.
An API gateway often still sits in front of the BFFs for TLS, rate limiting and routing. The gateway handles concerns shared by everyone; each BFF handles what is specific to its client.
4. The BFF Pattern in Java: Examples
These programs run on Java 21 with no libraries, so you can see the idea without framework code. In a real system each BFF would be a small Spring Boot application calling services over HTTP.
4.1 Shaping One Product for Two Clients
The Catalog Service returns everything it knows. Each BFF keeps only what its screen shows:
import java.math.BigDecimal;
import java.util.List;
public class ClientSpecificResponses {
// What the Catalog Service returns: everything it knows about a product
record Product(String id, String name, BigDecimal price, String description,
List<String> imageUrls, double rating, int reviewCount, List<String> specs) {}
// Each BFF shapes the same product for its own screen
record MobileCard(String id, String name, BigDecimal price, String thumbnail) {}
record WebDetail(String id, String name, BigDecimal price, String description,
List<String> imageUrls, String ratingText, List<String> specs) {}
static MobileCard mobileBff(Product p) {
return new MobileCard(p.id(), p.name(), p.price(), p.imageUrls().getFirst()); // small screen: one image
}
static WebDetail webBff(Product p) {
String rating = p.rating() + " stars from " + p.reviewCount() + " reviews"; // formatted for display
return new WebDetail(p.id(), p.name(), p.price(), p.description(), p.imageUrls(), rating, p.specs());
}
public static void main(String[] args) {
Product kettle = new Product("P-501", "Steel Kettle 1.7L", new BigDecimal("1499.00"),
"Fast-boil electric kettle with auto shut-off and a 360-degree base.",
List.of("/img/p501-front.jpg", "/img/p501-side.jpg", "/img/p501-box.jpg"),
4.6, 312, List.of("1.7 litre", "2200 W", "Stainless steel"));
MobileCard card = mobileBff(kettle);
WebDetail detail = webBff(kettle);
System.out.println("Mobile BFF -> " + card);
System.out.println("Web BFF -> " + detail);
System.out.println("Mobile response is " + card.toString().length() + " characters, web response is "
+ detail.toString().length());
}
}
Mobile BFF -> MobileCard[id=P-501, name=Steel Kettle 1.7L, price=1499.00, thumbnail=/img/p501-front.jpg]
Web BFF -> WebDetail[id=P-501, name=Steel Kettle 1.7L, price=1499.00, description=Fast-boil electric kettle with auto shut-off and a 360-degree base., imageUrls=[/img/p501-front.jpg, /img/p501-side.jpg, /img/p501-box.jpg], ratingText=4.6 stars from 312 reviews, specs=[1.7 litre, 2200 W, Stainless steel]]
Mobile response is 90 characters, web response is 294
The mobile response is less than a third of the size, and the web response arrives with the rating already formatted. Neither client had to change the Catalog Service.
4.2 Keeping Tokens Out of the Browser
This is the BFF's most important security role, and many tutorials miss it. If a single-page app stores access tokens in the browser, any injected script can steal them. With a BFF, the browser keeps only an HTTP-only session cookie, which JavaScript cannot read. The tokens stay on the server:
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class BffTokenHandler {
// Server-side session store inside the BFF: session id -> access token
static final Map<String, String> sessions = new HashMap<>();
// After the user logs in, the BFF keeps the token and gives the browser only a random session id
static String login(String accessToken) {
// Real BFFs use a random id (UUID.randomUUID or SecureRandom); this one is derived so the output repeats
String sessionId = UUID.nameUUIDFromBytes(accessToken.getBytes()).toString();
sessions.put(sessionId, accessToken);
return "SESSION=" + sessionId + "; HttpOnly; Secure; SameSite=Strict";
}
// Every browser request: look up the token and call the downstream service with it
static String getOrders(String sessionId) {
String token = sessions.get(sessionId);
if (token == null) {
return "401 please log in again";
}
return "GET /orders Authorization: Bearer " + token.substring(0, 8) + "... -> 200 OK";
}
static void logout(String sessionId) {
sessions.remove(sessionId); // the token is gone at once; nothing to clean up in the browser
}
public static void main(String[] args) {
String cookie = login("eyJhbGciOiJSUzI1NiJ9.asha.signature");
String sessionId = cookie.substring("SESSION=".length(), cookie.indexOf(';'));
System.out.println("Browser stores: Set-Cookie: " + cookie);
System.out.println("Browser sees a token? " + cookie.contains("eyJ"));
System.out.println("BFF calls: " + getOrders(sessionId));
logout(sessionId);
System.out.println("After logout: " + getOrders(sessionId));
}
}
Browser stores: Set-Cookie: SESSION=2e2c4e24-6c5e-3d9b-a25b-617ae0826256; HttpOnly; Secure; SameSite=Strict
Browser sees a token? false
BFF calls: GET /orders Authorization: Bearer eyJhbGci... -> 200 OK
After logout: 401 please log in again
This is often called the token handler approach. Spring Security supports it with the OAuth2 login client, and Spring Cloud Gateway can forward the stored token with its TokenRelay filter. Because the browser now sends cookies, the BFF must also protect against CSRF, for example with SameSite cookies and CSRF tokens. The session management guide covers sessions in more depth.
4.3 Supporting Old Mobile App Versions
A web app updates as soon as you deploy it, but users keep old mobile apps for months. The Mobile BFF can translate new service responses into the format old versions expect:
import java.math.BigDecimal;
import java.text.NumberFormat;
import java.util.Locale;
public class AppVersionAdapter {
record Order(String id, BigDecimal total, String status) {}
// Old app versions in users' phones expect a formatted price string and a different status name
static String forAppVersion(Order order, int appVersion) {
if (appVersion < 5) {
String price = NumberFormat.getCurrencyInstance(Locale.of("en", "IN")).format(order.total());
String status = order.status().equals("OUT_FOR_DELIVERY") ? "SHIPPED" : order.status(); // v4 does not know the new status
return "{\"orderId\":\"" + order.id() + "\",\"price\":\"" + price + "\",\"status\":\"" + status + "\"}";
}
return "{\"orderId\":\"" + order.id() + "\",\"total\":" + order.total() + ",\"status\":\"" + order.status() + "\"}";
}
public static void main(String[] args) {
Order order = new Order("ORD-1001", new BigDecimal("1499.00"), "OUT_FOR_DELIVERY");
System.out.println("App 4.2 -> " + forAppVersion(order, 4));
System.out.println("App 5.0 -> " + forAppVersion(order, 5));
}
}
App 4.2 -> {"orderId":"ORD-1001","price":"₹1,499.00","status":"SHIPPED"}
App 5.0 -> {"orderId":"ORD-1001","total":1499.00,"status":"OUT_FOR_DELIVERY"}
The Order Service is free to add the new OUT_FOR_DELIVERY status, and version 4 of the app keeps working.
5. BFF vs API Gateway vs General-Purpose API
| Aspect | General-purpose API | API gateway | BFF |
|---|---|---|---|
| Serves | All clients the same way | All clients, one entry point | One frontend only |
| Main job | Expose business data | Routing, TLS, rate limits, token checks | Combine and shape data for screens |
| Owned by | Backend team | Platform team | The frontend's team |
| Response shape | Generic | Passed through | Tailored per screen |
| Client-specific logic | Grows into a mess | Should not contain it | Its purpose |
They are not competitors. A common setup is a gateway at the edge, a BFF per frontend behind it, and general-purpose services behind the BFFs.
6. Common Mistakes and Pitfalls
- Business rules in the BFF. Price calculation or stock checks copied into two BFFs will drift apart. Business rules belong in the services; the BFF only shapes data.
- One BFF per device. Separate Android and iOS BFFs for identical screens double the work for no benefit.
- Duplicated plumbing. Each BFF writing its own HTTP clients, retries and logging. Share these through a library, not through shared business code.
- No timeouts or fallbacks. A BFF calls many services, so one slow service can slow every screen. Use the timeout pattern and a circuit breaker.
- A BFF without an owner. If a separate backend team must change the BFF for every screen, the old bottleneck is back.
7. Best Practices
- Let the frontend team own its BFF, so they can change screen and API together.
- Keep the BFF thin: aggregation, shaping and client concerns only.
- Call services in parallel, with a timeout on every call and fallbacks for optional data.
- Keep tokens server-side for browser apps, and use secure, HTTP-only,
SameSitecookies with CSRF protection. - Plan for old mobile versions, and track which versions are still in use before removing support.
- Trace requests across the BFF with correlation IDs, so you can see which service made a screen slow.
8. Summary
- The Backend for Frontend pattern gives each type of client its own backend, owned by the frontend team.
- It solves the one-size-fits-all API problem: over-fetching, too many calls and teams waiting on each other.
- A BFF combines service data, shapes it for one client and handles client concerns such as sessions and app versions.
- For browser apps, the BFF keeps tokens on the server and gives the browser only an HTTP-only cookie.
- Keep business rules in the services, and use the BFF pattern alongside an API gateway, not instead of one.
Backend for Frontend (BFF) Pattern: Interview Questions
Common interview questions on the Backend for Frontend pattern, from basic to scenario-based, with short answers.
Basic questions
Q. What is the Backend for Frontend (BFF) pattern?
Answer: The BFF pattern gives each type of client, such as the web app and the mobile app, its own backend. Each BFF calls the shared microservices, combines and shapes their data, and returns exactly what that client's screens need.
Q. What problem does the BFF pattern solve?
Answer: It solves the problems of one general-purpose API for all clients: mobile apps downloading fields they never show, several calls per screen, joining data on the device, and every frontend team waiting on one backend team for changes.
Q. Who should own a BFF?
Answer: The team that builds the frontend. Then the screen and its API can change together in one release, without asking another team. If a separate backend team owns every BFF, the original bottleneck returns.
Q. Do you need a separate BFF for Android and iOS?
Answer: Usually not. Create one BFF per user experience, not per device. If both apps show the same screens, one Mobile BFF serves both. Split only when the experiences, release cycles or teams really differ.
Intermediate questions
Q. What is the difference between a BFF and an API gateway?
Answer: An API gateway is one entry point for all clients. It handles shared concerns: routing, TLS, rate limiting and token checks, and it should not contain client-specific logic. A BFF serves one frontend and exists to hold that client's logic: aggregation and response shaping. They work together: the gateway at the edge, the BFFs behind it.
Q. How is a BFF different from the aggregator or API composition pattern?
Answer: Aggregation describes what is done: calling several services and combining the results. A BFF describes who it is done for: one specific frontend. A BFF usually performs aggregation, but also shapes responses and handles client concerns such as sessions and app versions.
Q. How does a BFF improve security for single-page applications?
Answer: The BFF completes the OAuth 2.0 login and keeps the access and refresh tokens on the server. The browser receives only a secure, HTTP-only session cookie, which JavaScript cannot read, so a cross-site scripting attack cannot steal the tokens. Because the browser now authenticates with cookies, the BFF must add CSRF protection, for example SameSite cookies and CSRF tokens.
Q. What should not go into a BFF?
Answer: Business rules, such as price calculation, discounts or stock checks. If two BFFs each implement them, they will drift apart and show different results. A BFF should only aggregate, filter, format and handle client concerns; the services own the rules and the data.
Q. How do you avoid duplicated code across several BFFs?
Answer: Some duplication of screen-shaping code is expected and fine. Shared technical plumbing, such as HTTP clients, retries, timeouts, logging and tracing, can go into a common library. If the same business logic appears in several BFFs, move it into a downstream service instead.
Q. Is GraphQL an alternative to the BFF pattern?
Answer: Partly. With GraphQL, each client asks for exactly the fields it needs from one schema, which removes much of the over-fetching a BFF fixes. But clients still share one schema and one server, and concerns like token handling or old app versions remain. Many teams run a GraphQL server as their BFF.
Tricky and scenario questions
Q. The Mobile BFF becomes slow whenever the Customer Service is slow. How do you fix it?
Answer: Set a timeout on every downstream call, call independent services in parallel, and add a circuit breaker so a failing service is not called again and again. Mark customer data as optional on screens that can live without it and return a fallback, so one slow service does not slow the whole screen.
Q. The web and mobile apps show different prices for the same product. What went wrong?
Answer: Most likely a business rule, such as a discount, was implemented in one BFF and not the other, or the two BFFs apply it differently. Move the price calculation into the Pricing or Catalog Service, and let both BFFs only display the price they receive.
Q. How does a Mobile BFF help with old app versions?
Answer: Users keep old app versions for months. The app sends its version, and the Mobile BFF converts new service responses into the format each version expects, for example mapping a new status to one the old app knows. Services can then evolve freely. Track version usage and remove adapters once old versions are no longer used.
Q. When should you not use the BFF pattern?
Answer: When you have only one frontend, when all clients need nearly the same data, or when a small team would have to maintain several extra services. A BFF adds a network hop and another service to deploy and monitor, so it should solve a real problem, not be added by default.