The Complete AEM Request Lifecycle
Following a Single Browser Request from URL to Rendered HTML
In Part 1 of this series, we explored the major building blocks of Adobe Experience Manager and how they fit together at a high level. We introduced concepts like Dispatcher, Apache Sling, Sling Models, OSGi Services, and HTL without diving too deeply into how they interact.
Now it's time to connect those pieces.
In this article, we'll follow a single browser request from the moment a user enters a URL until the final HTML is returned. Along the way, you'll see how each architectural layer contributes to the response and why understanding that lifecycle is one of the most valuable skills for debugging, designing components, and working effectively with AEM.
Reading time: 10β12 min
AEM Cloud Master Series
This article is Part 2 of a 10-part series designed to help frontend engineers and AEM developers understand Adobe Experience Manager from architecture to production deployment.
Series Roadmap
β Part 1 β Understanding Adobe Experience Manager Architecture
β Part 2 β The Complete AEM Request Lifecycle (Current)
Part 3 β Inside Apache Sling
Part 4 β Sling Models & Dependency Injection
Part 5 β OSGi Services Explained
Part 6 β HTL Explained
Part 7 β Dispatcher Deep Dive
Part 8 β Building Enterprise Components in AEM
Part 9 β React + Adobe AEM Cloud Service
Part 10 β From Development to Production
One Request, Many Systems
Imagine a user opens the following page:
https://www.example.com/products/laptop.html
From the visitor's perspective, it's simple.
They press Enter.
A page appears.
Behind the scenes, however, multiple systems collaborate before any HTML reaches the browser.
A simplified view looks like this:
Every box in this diagram has a single responsibility.
Understanding those responsibilities is one of the biggest differences between someone who knows AEM and someone who's simply memorized annotations.
Step 1 β The Browser Sends a Request
Everything starts with an HTTP request.
GET /products/laptop.html HTTP/1.1
Host: www.example.com
At this point, AEM hasn't done anything yet.
The browser simply wants an HTML page.
The request first travels across the internet toward Adobe's edge infrastructure.
Step 2 β Adobe CDN
The first AEM-related layer isn't actually AEM.
It's usually the Adobe Content Delivery Network (CDN).
The CDN sits close to users around the world and stores previously generated pages.
Its goal is simple:
Avoid reaching AEM whenever possible.
If the page is already cached, the response can be returned immediately.
No Publish instance is involved.
No Java executes.
No component renders.
The user receives the page much faster.
Why This Matters
Developers often think performance starts with writing efficient Java code.
In AEM, performance starts much earlier.
If the CDN can answer the request, the application doesn't need to run at all.
That's one of the reasons enterprise AEM sites can handle millions of page views without rendering every request from scratch.
Step 3 β Dispatcher
If the CDN doesn't have a cached copy, the request continues to Dispatcher.
Dispatcher is often described as "AEM's cache."
While that's true, it's only part of its job.
Dispatcher is also responsible for protecting Publish.
Before forwarding a request, it evaluates:
Is this URL allowed?
Is the request cacheable?
Does a valid cached response already exist?
A simplified decision flow looks like this:
This means many requests never reach Publish.
Either the CDN or Dispatcher returns the response instead.
That dramatically reduces server load and improves response times.
CDN vs Dispatcher
A common source of confusion is the difference between the CDN and Dispatcher.
They both cache content, but they operate at different layers.
| Layer | Primary Responsibility |
|---|---|
| Adobe CDN | Global edge caching close to visitors |
| Dispatcher | Security filtering and local AEM caching |
You can think of them as two checkpoints.
The CDN answers the request if it already has the page.
If not, Dispatcher gets the next opportunity before AEM Publish has to render anything.
(Continue with Part 2: Publish, Apache Sling, Resource Resolution, JCR, and sling:resourceType.)
Step 4 β AEM Publish
If neither the CDN nor Dispatcher can satisfy the request, it finally reaches an AEM Publish instance.
This is where AEM begins doing real work.
Unlike the Author environment, which is designed for creating and editing content, Publish has a single responsibility:
Render content for visitors.
By the time a request arrives here, several expensive operations have already been avoided thanks to the CDN and Dispatcher. That allows Publish to focus on generating pages instead of handling every incoming request.
What Happens Inside Publish?
Once Publish receives the request, it doesn't immediately generate HTML.
Instead, it hands the request to Apache Sling, the web framework at the heart of AEM.
You can think of Publish as the application server and Sling as the traffic controller inside it.
Publish hosts the application.
Sling decides what should be rendered.
Step 5 β Apache Sling Takes Over
This is where AEM starts to feel different from traditional web frameworks.
Imagine you're using Spring Boot.
A request like this:
/products/laptop
is usually mapped to a controller.
@GetMapping("/products/{id}")
public Product getProduct(...) {
...
}
AEM doesn't work like that.
Instead, Sling asks a different question:
"Which content resource does this URL represent?"
That single question drives the entire rendering process.
Step 6 β Resource Resolution
Suppose the visitor requests:
/products/laptop.html
Sling now needs to determine which content that URL represents.
This process is called Resource Resolution.
It converts a URL into a resource stored inside the repository.
Notice something important.
The URL is not directly mapped to a Java class.
It's mapped to content.
That's why AEM is called resource-driven.
Resource vs ResourceResolver
These two terms are easy to confuse.
They're related, but they are not the same thing.
| Term | Responsibility |
|---|---|
| ResourceResolver | Finds and retrieves resources |
| Resource | Represents a piece of content inside the repository |
Think of it like this.
If the JCR is a library:
the ResourceResolver is the librarian,
the Resource is the book you asked for.
The librarian helps you find the book.
The librarian is not the book.
Step 7 β Reading from the JCR
Once Sling has identified the correct resource, it retrieves it from the Java Content Repository (JCR).
Unlike a traditional relational database, the JCR stores information as a tree of nodes.
A simplified example might look like this:
/content
βββ my-site
βββ products
βββ laptop
βββ jcr:content
βββ title = "Surface Laptop"
βββ description = "..."
βββ sling:resourceType = "my-site/components/product"
Every page, asset, and component configuration eventually lives somewhere inside this hierarchy.
Sling isn't reading rows from database tables.
It's navigating a content tree.
Step 8 β sling:resourceType
This property is one of the most important concepts in AEM.
Every resource tells Sling which component should render it.
For example:
sling:resourceType = my-site/components/product
Sling now knows exactly where to continue.
This is one of the biggest architectural differences between AEM and MVC frameworks.
Traditional frameworks typically route to a controller.
AEM routes to content, and the content itself decides which component is responsible for rendering it.
Why sling:resourceType Matters
When a component doesn't render correctly, one of the first things experienced AEM developers check is the sling:resourceType.
If it's incorrect, Sling may:
render the wrong component,
render nothing,
or fail to find a matching script.
Many rendering issues have nothing to do with Java code.
They're simply caused by an incorrect resource type.
That's why understanding this property is so important when debugging AEM applications.
At this point in the request lifecycle, Sling has:
received the request,
resolved the URL,
found the correct content,
read the resource from the JCR,
identified which component should render it.
The next step is preparing the data for that component before any HTML is generated. That's where Sling Models, OSGi Services, and HTL come into the picture.
Step 9 β Preparing the Data with Sling Models
At this stage, Sling has identified what should be rendered.
The next question is:
"What data does this component need?"
Although the JCR already contains the authored content, it's often not in the exact format the component needs.
For example:
Dates may need formatting.
Image paths may need to become public URLs.
Links may need validation.
Content from multiple resources may need to be combined.
Additional data may need to come from an external service.
Rather than placing this logic inside the HTML template, AEM uses Sling Models.
Think of a Sling Model as the bridge between the repository and the presentation layer.
The model receives raw content and prepares everything needed for rendering.
A Simple Example
Suppose a Hero component stores only the following properties:
title = "Welcome"
publishDate = "2026-08-01"
The component wants to display:
Welcome
Published August 1, 2026
Instead of formatting the date inside HTL, the Sling Model prepares it first.
@Model(adaptables = SlingHttpServletRequest.class)
public class HeroModel {
@ValueMapValue
private String title;
@ValueMapValue
private LocalDate publishDate;
public String getTitle() {
return title;
}
public String getFormattedDate() {
return publishDate.format(
DateTimeFormatter.ofPattern("MMMM d, yyyy")
);
}
}
Now the template becomes very simple.
<sly data-sly-use.model="com.example.core.models.HeroModel"/>
<h1>${model.title}</h1>
<p>${model.formattedDate}</p>
Notice the separation of responsibilities.
The model prepares the data.
HTL simply renders it.
Step 10 β Calling an OSGi Service
Not every piece of data comes from the JCR.
Many enterprise components also need information from outside AEM.
For example:
Azure Search
Product APIs
Pricing services
AI services
Workfront
CRM systems
That logic doesn't belong inside a Sling Model.
Instead, Sling Models delegate reusable business logic to OSGi Services.
A simplified example looks like this.
@Model(adaptables = SlingHttpServletRequest.class)
public class SearchModel {
@OSGiService
private SearchService searchService;
public List<Result> getResults() {
return searchService.search("laptop");
}
}
The Sling Model doesn't know how Azure Search works.
It simply asks the service for data.
That keeps components clean, reusable, and easier to test.
Step 11 β Rendering with HTL
Once all the data has been prepared, the final step inside AEM is rendering the page.
This is the responsibility of HTL (HTML Template Language).
HTL combines:
authored content,
prepared model data,
component structure,
to generate the final HTML sent to the browser.
A well-designed HTL file contains very little logic.
Its primary job is presentation.
<sly data-sly-use.model="com.example.core.models.ProductModel"/>
<article class="product">
<h1>${model.title}</h1>
<p>${model.description}</p>
<img src="${model.image}" alt="${model.altText}" />
</article>
At this point, AEM has finished its work.
The generated HTML is returned to Dispatcher, which may cache it before sending it back through the CDN to the user's browser.
Putting It All Together
Let's look at the complete journey one more time.
Although there are many moving parts, every layer has a clear responsibility.
That's one of the strengths of AEM's architecture.
Instead of one large application doing everything, the platform divides responsibilities into specialized layers that work together.
By understanding this flow, debugging becomes much easier.
When a page doesn't render correctly, you can work through the pipeline step by step:
Did the request reach Publish?
Did Sling resolve the correct resource?
Is the
sling:resourceTypecorrect?Did the Sling Model prepare the expected data?
Did the OSGi Service return valid results?
Is the HTL template rendering the model correctly?
Following that sequence is far more effective than jumping straight into Java code, because each layer eliminates an entire category of possible problems before moving to the next one.
Common Mistakes
After working on AEM projects, I've noticed that many developers make the same mistakes when they're first learning the platform.
Most of them come from treating AEM like a traditional MVC framework.
Here are a few of the most common ones.
Expecting Controllers
One of the biggest mindset shifts is realizing that AEM doesn't begin by finding a controller.
Instead, it begins by finding a resource.
If you're constantly asking:
"Where is the controller for this page?"
you're approaching AEM with the wrong mental model.
Instead, ask:
"Which resource is Sling trying to resolve?"
That question usually leads you to the right place much faster.
Putting Business Logic in HTL
HTL is designed for rendering HTML.
It's not the place for formatting dates, calling APIs, or implementing business rules.
A common guideline is:
HTL renders
Sling Models prepare data
OSGi Services contain reusable business logic
Keeping those responsibilities separate makes components easier to test, debug, and maintain.
Ignoring the Cache
Sometimes developers immediately start debugging Java code when a page doesn't display the expected content.
Before doing that, ask yourself:
Is the page coming from the CDN?
Is Dispatcher serving a cached response?
Has the cache been invalidated?
Many "application bugs" turn out to be caching issues.
Understanding where the response came from is often the first step in solving the problem.
Thinking Every Request Reaches Publish
Another common misconception is assuming that every browser request is rendered by AEM.
In a healthy production environment, that's often not the case.
If the CDN or Dispatcher already has a valid cached response, Publish may never see the request at all.
That's by design.
Enterprise AEM deployments are built to avoid unnecessary rendering whenever possible.
The Complete Request Lifecycle at a Glance
If you only remember one sequence from this article, remember this one.
Browser
β
βΌ
Adobe CDN
β
βΌ
Dispatcher
β
βΌ
AEM Publish
β
βΌ
Apache Sling
β
βΌ
Resource Resolution
β
βΌ
JCR Resource
β
βΌ
sling:resourceType
β
βΌ
Component
β
βΌ
Sling Model
β
βΌ
OSGi Service (if needed)
β
βΌ
HTL
β
βΌ
Generated HTML
β
βΌ
Browser
This sequence is worth memorizing.
It's not only how AEM processes a request, but also one of the most useful mental models for troubleshooting production issues.
Key Takeaways
If you finish this article with these ideas in mind, you've already built a solid understanding of how AEM handles requests.
A request doesn't always reach AEM. The CDN and Dispatcher may return a cached response first.
Apache Sling is responsible for resolving resourcesβnot routing requests to controllers.
The JCR stores the content that Sling retrieves.
The
sling:resourceTypeproperty tells Sling which component should render a resource.Sling Models prepare data for presentation.
OSGi Services handle reusable business logic.
HTL transforms prepared data into HTML.
Every layer has a single responsibility, making the platform easier to scale and maintain.
Final Thoughts
One of the best pieces of advice I received early in my AEM journey was:
Don't memorize the technologies. Understand the flow.
When you understand how a single request moves through the platform, individual concepts like Sling Models, HTL, Dispatcher, and OSGi stop feeling like isolated technologies. Instead, they become parts of a single pipeline, each solving a specific problem.
That shift in perspective makes debugging easier, architecture discussions clearer, and learning new parts of AEM much more intuitive.
Whether you're building components, integrating external services, or troubleshooting production issues, the request lifecycle is the foundation everything else is built on.
Continue the Series
You've now followed a complete browser request through Adobe Experience Manager, from the first HTTP request to the final HTML returned to the browser.
Understanding this lifecycle gives you a practical framework for debugging, designing components, and reasoning about performance in AEM. More importantly, it explains how the platform's architectural layers work together instead of treating them as isolated technologies.
In the next article, we'll focus on the engine behind that entire process: Apache Sling. We'll explore how Sling resolves resources, how ResourceResolver works, why sling:resourceType is so important, and how AEM decides which component should render a request.
Next Article β Part 3: Inside Apache Sling
Masoud
July 12th, 2025