Understanding URL Resolution in AEM as a Cloud Service: Sling Mappings, Aliases, Vanity URLs and Dispatcher
Introduction
URL management in Adobe Experience Manager can look deceptively simple.
A visitor requests:
https://www.example.com/es/cursos/java
while the corresponding AEM resource might live at:
/content/acme-learning/es/courses/java
Somewhere between the browser and the JCR repository, the public URL has to become an AEM resource.
But that is only half of the problem.
When AEM generates a navigation link, canonical URL or sitemap entry, the reverse operation must also work:
/content/acme-learning/es/courses/java
↓
https://www.example.com/es/cursos/java
AEM provides several mechanisms that participate in this process:
-
Apache HTTP Server
mod_rewrite -
Dispatcher
-
Sling Resource Mapping
-
ResourceResolver.resolve() -
ResourceResolver.map() -
sling:alias -
sling:vanityPath -
redirects
-
sitemap generation
The difficulty is usually not understanding what each feature does individually.
The difficult part is knowing which layer should solve which URL problem.
This article follows a request from the public URL to the AEM resource and then travels in the opposite direction. Along the way, we will clarify when to use Dispatcher rewrites, Sling mappings, aliases, vanity URLs and redirects, and how those decisions affect caching, multilingual URLs, canonical URLs and sitemaps.
1. One URL, multiple resolution layers
Consider a fictional international learning platform called Acme Learning.
Its public website exposes:
https://www.example.com/es/cursos/java
but the AEM repository contains:
/content/acme-learning/es/courses/java
The request can pass through several layers:
Browser
│
▼
Adobe-managed CDN
│
▼
Apache HTTP Server
│
├── mod_rewrite
│
▼
Dispatcher
│
▼
Sling Resource Resolver
│
├── mappings
├── aliases
└── vanity paths
│
▼
AEM Resource
│
▼
Component rendering
Each layer has a different responsibility.
One of the easiest ways to create a difficult-to-maintain AEM architecture is to spread URL logic randomly across all of them.
For example:
CDN rewrites part of the path
↓
Dispatcher rewrites another part
↓
Sling mapping rewrites it again
↓
custom Java reconstructs the URL
It may work initially, but understanding how a URL is resolved later becomes unnecessarily difficult.
A better approach is to establish clear responsibilities.
2. External redirects and internal rewrites are not the same thing
Before discussing AEM-specific features, it helps to distinguish two fundamental HTTP concepts.
External redirect
Suppose an old URL is:
/academy/java-basics
and the content has permanently moved to:
/courses/java
A redirect tells the browser that it should use a different URL:
GET /academy/java-basics
HTTP/1.1 301 Moved Permanently
Location: /courses/java
The browser then requests:
/courses/java
The user sees the new URL.
Conceptually:
Browser URL A
│
▼
HTTP 301
│
▼
Browser URL B
Internal rewrite
An internal rewrite is different.
The visitor requests:
/es/cursos/java
Apache may internally transform it into:
/content/acme-learning/es/courses/java
while the browser continues displaying:
/es/cursos/java
Conceptually:
Browser
/es/cursos/java
│
▼
Apache internal rewrite
│
▼
/content/acme-learning/es/courses/java
│
▼
AEM
A useful rule of thumb is:
A redirect changes the URL the client uses. A rewrite changes how the server resolves that URL.
This distinction becomes important when deciding whether a URL change is part of public information architecture or merely an implementation detail.
3. Dispatcher and mod_rewrite
AEM content normally lives below /content.
A repository structure such as:
/content/acme-learning/en
/content/acme-learning/es
/content/acme-learning/fr
is useful internally.
It does not mean that /content/acme-learning should appear in public URLs.
Adobe's URL-management guidance specifically discusses using Apache mod_rewrite to transform shortened public URLs back into their longer repository form before they reach Dispatcher. One important advantage is cache consistency: Dispatcher caches using the internal resource path, which also aligns better with cache invalidation requests coming from AEM.
For example:
RewriteRule ^/(en|es|fr)/(.*)$ \
/content/acme-learning/$1/$2 [PT,L]
Conceptually:
Public
/es/courses/java.html
↓
Apache
/content/acme-learning/es/courses/java.html
↓
Dispatcher cache
/content/acme-learning/es/courses/java.html
This keeps the repository prefix out of the public URL while still allowing Dispatcher to work with the actual AEM hierarchy.
The exact rewrite rules depend on the site's architecture, but an important principle is:
Dispatcher should normalize HTTP requests, not become a second content repository containing hundreds of business-specific routing rules.
If every new page or content category requires a new Apache rule, URL responsibilities are probably leaking into the wrong layer.
4. Sling Resource Mapping
Once a request reaches Sling, the Resource Resolver determines which resource the URL represents.
Sling distinguishes between two related operations:
resolve()
URL
↓
Resource
and:
map()
Resource
↓
URL
Apache Sling explicitly describes these as incoming mapping and reverse/outgoing mapping.
This distinction is fundamental.
Incoming mapping: resolve()
Given something such as:
/es/courses/java
Sling needs to locate:
/content/acme-learning/es/courses/java
Conceptually:
Resource resource =
resourceResolver.resolve(request, path);
Outgoing mapping: map()
Starting from:
/content/acme-learning/es/courses/java
AEM may need to produce:
/es/courses/java
Conceptually:
String url =
resourceResolver.map(
request,
resource.getPath()
);
Apache Sling describes map() as the reverse operation of resolve(), designed so that the mapped URL can resolve back to the same resource.
This bidirectional model is one of the most important concepts in AEM URL architecture.
5. Why inbound resolution alone is not enough
Imagine that this URL works correctly:
https://www.example.com/es/courses/java
and resolves to:
/content/acme-learning/es/courses/java
It is tempting to conclude that URL configuration is complete.
But suppose the site's navigation generates:
/content/acme-learning/es/courses/java.html
or the sitemap contains:
https://publish-instance/content/acme-learning/es/courses/java.html
Then only one direction is correct:
URL → Resource
✓
while:
Resource → URL
✗
is still wrong.
This is why URL architecture should always be tested in both directions:
PUBLIC URL
│
│ resolve()
▼
AEM RESOURCE
│
│ map()
▼
PUBLIC URL
Ideally:
resolve(map(resource))
should lead back to the same resource.
Adobe's SEO and URL management guidance explicitly recommends using ResourceResolver.map() when custom components output URLs after the Sling mappings have been defined.
For example:
for (Page course : courses) {
String courseUrl =
resourceResolver.map(
request,
course.getPath()
);
// Use courseUrl in the generated link
}
This avoids hard-coding assumptions such as:
"/" + language + page.getPath()
throughout component code.
6. sling:alias: public names without renaming JCR resources
Multilingual sites introduce another interesting problem.
Suppose Acme Learning maintains the same JCR page name across languages:
/content/acme-learning/en/courses
/content/acme-learning/es/courses
/content/acme-learning/fr/courses
Keeping the same resource name can simplify translation relationships and content synchronization.
But Spanish visitors might reasonably expect:
/es/cursos
instead of:
/es/courses
This is exactly the type of problem sling:alias can solve.
The Spanish resource can remain:
/content/acme-learning/es/courses
while defining:
sling:alias = cursos
The public path can then use:
/es/cursos
without renaming the JCR node.
Adobe specifically recommends sling:alias for localized page names while retaining consistent page names across language structures.
Apache Sling also documents an important detail: sling:alias participates not only in incoming resolution but also in outgoing mapping. If multiple aliases exist, the first one becomes the preferred value for outgoing mapping.
Conceptually:
JCR
/content/acme-learning/es/courses
│
│ sling:alias=cursos
▼
Public
/es/cursos
This gives us an important separation:
repository identity
≠
public URL name
That is particularly useful in multilingual content architectures.
7. Alias is not a vanity URL
sling:alias and vanity URLs can both make a resource accessible using another name, but they solve different problems.
Consider:
/content/acme-learning/es/courses/java
Alias scenario
The resource name is:
courses
while Spanish should expose:
cursos
Using:
sling:alias = cursos
produces a localized position inside the existing hierarchy:
/es/cursos/java
The alias is part of resource resolution.
Vanity URL scenario
Now suppose the marketing team launches a temporary campaign for a Java certification course.
The canonical page remains:
/es/cursos/java-certification
but they want a memorable promotional URL:
/java-pro
That is a much better vanity URL use case.
Conceptually:
/java-pro
│
▼
/content/acme-learning/es/courses/java-certification
Adobe describes vanity URLs as an author-controlled mechanism for making an existing page available from an additional, often shorter or promotional, URL. Adobe also warns that multiple URLs exposing the same page can fragment SEO value, making canonical URL management important.
A simple distinction is therefore:
sling:alias
↓
alternative name inside the resource hierarchy
vanity URL
↓
additional shortcut to the resource
They should not be treated as interchangeable features.
8. Vanity URLs have important constraints
Vanity URLs are convenient because authors can configure them from Page Properties.
For example:
Canonical page:
/es/cursos/cloud-architecture
Vanity:
/cloud-masterclass
But they are not intended to become a general-purpose routing engine.
Adobe currently documents several important constraints: vanity URLs must be unique, do not support regular-expression patterns, and should not collide with an existing page path.
This means something like:
/course/*
is not a valid strategy for dynamically mapping an entire section.
Vanity URLs are better thought of as explicit shortcuts.
Examples:
/spring-sale
/developer-week
/free-course
/cloud-masterclass
not as:
/{language}/{category}/{slug}
routing infrastructure.
9. Vanity URLs and Dispatcher
Even if Sling knows a vanity URL, Dispatcher must allow the request to reach AEM.
Adobe Dispatcher provides a specific /vanity_urls capability for this.
Dispatcher periodically retrieves the known vanity paths from AEM and stores them locally. If a request would normally be denied by Dispatcher filters, Dispatcher can check whether that path is a valid published vanity URL before deciding whether to allow it.
A simplified configuration looks like:
/vanity_urls {
/url "/libs/granite/dispatcher/content/vanityUrls.html"
/file "/tmp/vanity_urls"
/delay 300
}
This avoids adding a Dispatcher filter rule manually every time an author creates another vanity URL.
Conceptually:
Author creates vanity
│
▼
AEM Publish
│
▼
Vanity URL list
│
▼
Dispatcher local list
│
▼
Request can be allowed
This is another good example of why URL resolution cannot always be understood by looking only at Sling.
A valid Sling mapping can still fail earlier in the delivery stack if Dispatcher does not permit the request.
10. Sling mappings and /etc/map
Sling Resource Mapping can be configured using mapping definitions, traditionally located under:
/etc/map
The default mapping location is controlled by the Apache Sling Resource Resolver configuration.
Mappings can influence both incoming resource resolution and outgoing URL generation. Adobe's Resource Mapping documentation describes the two mapping tables explicitly:
Resolver Map Entries
used by:
ResourceResolver.resolve()
and:
Mapping Map Entries
used by:
ResourceResolver.map()
For example, a mapping could conceptually allow:
https://www.example.com/es/courses/java
to represent:
/content/acme-learning/es/courses/java
However, there is an important architectural consideration.
Adobe's current SEO and URL-management guidance warns that relying on /etc/map alone for shortened inbound URLs can produce Dispatcher cache-invalidation mismatches.
For example:
Public cache path:
/es/courses/java.html
while the AEM invalidation request references:
/content/acme-learning/es/courses/java
If Dispatcher has cached only the shortened path, the expected cache entry may not be invalidated.
Adobe therefore describes an architecture where Apache mod_rewrite expands the incoming public path before Dispatcher caches it, while Sling Resource Resolver mappings handle URL output.
This produces a useful division:
INCOMING
Public URL
↓
Apache mod_rewrite
↓
Internal AEM path
↓
Dispatcher
↓
AEM
OUTGOING
AEM resource
↓
ResourceResolver.map()
↓
Public URL
This is often easier to reason about from both a caching and content-delivery perspective.
11. A multilingual example
Let's combine the pieces using a completely fictional content structure.
Acme Learning stores:
/content/acme-learning/en/courses/java
/content/acme-learning/es/courses/java
/content/acme-learning/fr/courses/java
The desired public URLs are:
/en/courses/java
/es/cursos/java
/fr/formations/java
The repository names remain stable:
courses
while the localized language roots define aliases:
English
/content/acme-learning/en/courses
sling:alias = courses
Spanish
/content/acme-learning/es/courses
sling:alias = cursos
French
/content/acme-learning/fr/courses
sling:alias = formations
The architecture becomes:
/es/cursos/java
│
▼
Apache rewrite
│
▼
/content/acme-learning/es/cursos/java
│
▼
Sling alias resolution
│
▼
/content/acme-learning/es/courses/java
The outgoing direction becomes:
/content/acme-learning/es/courses/java
│
▼
ResourceResolver.map()
│
▼
/es/cursos/java
This keeps:
stable repository names
while still allowing:
localized public URLs
That distinction becomes increasingly valuable as the number of supported languages grows.
12. Redirects belong to a different problem
Consider a course whose old public URL was:
/es/cursos/java-8
and whose new permanent location is:
/es/cursos/java
This is not an alias problem.
It is not primarily a vanity problem either.
We explicitly want browsers and search engines to learn that the previous URL has moved.
The correct conceptual operation is:
/es/cursos/java-8
│
▼
301 Moved Permanently
│
▼
/es/cursos/java
Compare that with an internal rewrite:
/es/cursos/java
│
▼
internal AEM path
or a vanity:
/java
│
▼
existing course page
The intent determines the mechanism.
A good URL architecture should therefore distinguish:
resource naming
routing
shortcuts
migration
rather than trying to solve all four using the same feature.
13. The connection with canonical URLs
Suppose this page is available at:
/es/cursos/java-certification
and also through the vanity:
/java-pro
Both URLs expose the same content.
That can create duplicate URL representations from a search-engine perspective.
Adobe explicitly recommends considering canonical URLs when vanity URLs expose a page from additional locations.
The HTML might therefore identify:
<link
rel="canonical"
href="https://www.example.com/es/cursos/java-certification"
/>
The architectural lesson is that:
URL resolution
and:
canonical URL selection
are related but different responsibilities.
AEM may successfully resolve multiple URLs to the same resource, but SEO still needs one preferred identity.
14. Sitemaps expose outgoing URL problems
Sitemaps are one of the easiest places to detect an incomplete URL architecture.
Suppose users can correctly browse:
https://www.example.com/es/cursos/java
but the generated sitemap contains:
/content/acme-learning/es/courses/java.html
or only:
/es/courses/java
instead of:
https://www.example.com/es/cursos/java
The incoming URL may work perfectly.
The outgoing mapping does not.
AEM uses the Apache Sling Sitemap module for sitemap generation. Adobe's current sitemap documentation states that absolute sitemap URLs are supported through Sling mappings, generally configured on the AEM Publish service that generates the sitemap.
That creates another useful mental model:
JCR resource
│
▼
Sling mapping
│
▼
external URL
│
▼
sitemap entry
For this reason, whenever a sitemap contains incorrect hosts, internal /content paths or unexpected names, checking outgoing Sling mappings should be part of the investigation.
Sitemaps are not an isolated SEO feature.
They depend on the same URL architecture used elsewhere in the application.
15. Debugging URL resolution in AEM as a Cloud Service
When troubleshooting URL resolution, it is important to distinguish between the local AEM SDK and managed AEM as a Cloud Service environments.
The Sling JCR Resource Resolver console:
/system/console/jcrresolver
is available when running the AEM as a Cloud Service SDK locally, where the Web Console can be used for development and debugging.
It should not be considered a debugging tool available on managed AEM as a Cloud Service Author or Publish instances.
Adobe explicitly states that the Web Console is not available on managed AEM as a Cloud Service environments. Runtime information for cloud environments is instead exposed through the read-only AEM Developer Console available from Cloud Manager.
Therefore, URL-resolution debugging should follow different approaches locally and in cloud environments.
Local development
When running the AEM SDK locally, /system/console/jcrresolver is particularly useful for testing Sling Resource Resolver behavior.
It allows developers to inspect:
Resolver Map Entries
used by:
ResourceResolver.resolve()
and:
Mapping Map Entries
used by:
ResourceResolver.map()
It can also be used to test both directions directly:
Public URL
│
│ Resolve
▼
AEM Resource
and:
AEM Resource
│
│ Map
▼
Public URL
For example:
Resolve:
/es/cursos/java
↓
/content/acme-learning/es/courses/java
and:
Map:
/content/acme-learning/es/courses/java
↓
/es/cursos/java
This makes the local SDK an effective environment for validating Sling mappings before deploying them.
Managed AEM as a Cloud Service environments
In managed AEMaaCS environments:
/system/console/*
is not exposed.
AEM as a Cloud Service treats runtime code and configuration as immutable. OSGi configurations should be maintained as code and deployed through Cloud Manager rather than changed through the Web Console.
The AEM Developer Console, accessible from Cloud Manager for each environment, provides read-only runtime information such as:
-
OSGi configurations;
-
bundles;
-
components;
-
services;
-
servlets;
-
runtime status information.
However, it does not provide the same interactive Resolve and Map testing interface as the local JCR Resource Resolver console.
For cloud environments, URL-resolution debugging therefore becomes a combination of:
Deployed configuration
+
HTTP request testing
+
Dispatcher logs
+
AEM logs
+
Developer Console inspection
A practical troubleshooting flow is:
Public request
│
▼
Does the CDN/Apache layer return the expected response?
│
▼
Does the Dispatcher rewrite produce the expected internal path?
│
▼
Does AEM resolve that internal path?
│
▼
Does the generated HTML contain the expected mapped URLs?
│
▼
Are canonical and sitemap URLs correct?
For example, an incoming request can be tested directly:
curl -I https://www.example.com/es/cursos/java
or with verbose output when redirects and headers need to be inspected:
curl -v https://www.example.com/es/cursos/java
Dispatcher rewrite logging can then be used when the issue appears to be in the Apache layer.
If the problem is with outgoing URLs, inspect the actual output generated by the application:
<a href="/es/cursos/java">
or:
<link
rel="canonical"
href="https://www.example.com/es/cursos/java"
/>
as well as the generated sitemap.
This distinction is important:
/system/console/jcrresolveris an excellent local Sling debugging tool, but it should not be presented as a console available on managed AEM as a Cloud Service environments.
The recommended workflow is therefore:
LOCAL SDK
/system/console/jcrresolver
↓
Validate resolve() and map()
↓
Deploy configuration
CLOUD
Cloud Manager deployment
↓
AEM Developer Console
+
HTTP tests
+
Dispatcher/AEM logs
↓
Validate runtime behavior
This is much more efficient than changing rewrite rules until the problem appears to disappear.
16. A useful mental model
The complete architecture can be summarized as two flows.
Incoming
PUBLIC URL
│
▼
Adobe CDN
│
▼
Apache mod_rewrite
│
▼
Dispatcher
│
▼
Sling Resource Resolver
│
├── mapping
├── sling:alias
└── sling:vanityPath
│
▼
AEM RESOURCE
Outgoing
AEM RESOURCE
│
▼
ResourceResolver.map()
│
├── Sling mappings
└── sling:alias
│
▼
PUBLIC URL
│
├── navigation links
├── canonical URLs
├── alternate links
└── sitemap URLs
Thinking about both flows separately makes many AEM URL problems much easier to understand.
17. Which mechanism should I use?
| Requirement | Recommended mechanism |
|---|---|
Hide /content/acme-learning from visitors | Apache rewrite + Sling outgoing mapping |
| Convert a public path into an internal AEM path | Internal rewrite / Sling mapping |
Localize courses as cursos while keeping the JCR node name | sling:alias |
Provide /java-pro as an additional promotional shortcut | Vanity URL |
| Permanently move an old URL | HTTP 301 redirect |
| Temporarily redirect traffic | Appropriate HTTP redirect |
| Generate public links from JCR paths | ResourceResolver.map() |
| Generate correct absolute sitemap URLs | Sling mappings |
| Debug inbound mapping | JCR Resource Resolver → Resolve |
| Debug outbound mapping | JCR Resource Resolver → Map |
| Expose author-created vanity URLs through Dispatcher | Dispatcher /vanity_urls |
The important idea is that these mechanisms are complementary.
They are not competing implementations of the same feature.
18. Common mistakes
Treating sling:alias and vanity URLs as equivalent
An alias changes how a resource can be addressed inside its hierarchy.
A vanity URL creates an additional shortcut.
Their semantics are different.
Implementing every URL rule in Dispatcher
Apache is excellent at normalizing and routing HTTP requests.
It should not become the place where the application's entire content model is reproduced.
Solving only incoming URLs
Making:
/es/cursos/java
resolve correctly does not guarantee that AEM will generate that same URL in navigation, canonical tags or sitemaps.
Always test both:
resolve()
and:
map()
Generating URLs manually
Avoid code such as:
String url =
"/" + language
+ page.getPath()
+ ".html";
when Sling already provides the URL mapping abstraction.
Use:
resourceResolver.map(...)
when generating public URLs from repository paths.
Using vanity URLs as a dynamic routing engine
Vanity URLs are explicit paths and do not support regex patterns.
They are excellent shortcuts.
They are not replacements for structured URL mappings.
Ignoring cache invalidation
An inbound mapping may resolve perfectly while Dispatcher caches the page using a path that does not correspond to the invalidation path sent by AEM.
URL architecture and caching architecture must therefore be considered together. Adobe explicitly documents this risk when discussing /etc/map and shortened URLs.
19. Key takeaways
AEM URL management becomes much easier when each mechanism is given a clear responsibility.
The most important principles are:
-
Think about URLs in two directions.
Incoming resolution:
URL → Resourceand outgoing mapping:
Resource → URLare related but separate operations.
-
Use Apache and Dispatcher for HTTP-level routing and normalization.
They are particularly useful for hiding repository prefixes while maintaining cache consistency.
-
Use Sling Resource Mapping for resource-to-URL abstraction.
resolve()identifies resources from URLs, whilemap()produces external URLs from resource paths. -
Use
sling:aliasfor alternative or localized resource names.This can preserve stable JCR names while allowing translated public paths.
-
Use vanity URLs for explicit alternative shortcuts.
They are particularly useful for memorable campaign or promotional paths, but they are not a general routing system.
-
Use redirects when the client needs to learn about a new URL.
Internal rewrites and external redirects solve fundamentally different problems.
-
Remember that URL architecture affects caching and SEO.
Dispatcher invalidation, canonical URLs, generated links and sitemaps all depend on consistent URL decisions.
Perhaps the most useful mental model is this:
A public URL is not the JCR path. It is a representation of that resource produced by several cooperating layers.
A well-designed AEM implementation makes that translation predictable in both directions.
References
[1] Adobe Experience League — SEO and URL Management Best Practices for Adobe Experience Manager as a Cloud Service
Covers URL rewriting, localized page names using sling:alias, Sling Resource Resolver mappings, ResourceResolver.map(), Apache mod_rewrite, canonical URLs and the Dispatcher cache-invalidation implications of different URL-mapping strategies.
Adobe Experience League — SEO and URL Management Best Practices
[2] Adobe Experience League — Resource Mapping
Official AEM as a Cloud Service documentation covering Sling resource mappings, redirects, vanity URLs, resolve() and map() mapping tables, and the JCR Resource Resolver console.
Adobe Experience League — Resource Mapping
[3] Apache Sling — Mappings for Resource Resolution
Apache Sling's detailed documentation for incoming and outgoing mappings, aliases, vanity paths and reverse mapping.
Apache Sling — Mappings for Resource Resolution
[4] Adobe Experience League — Page Properties
Documents the AEM page properties for Vanity URL, Redirect Vanity URL, Canonical URL and sitemap generation, including current vanity URL restrictions.
Adobe Experience League — Page Properties
[5] Adobe Experience League — Configure AEM Dispatcher
Official Dispatcher configuration documentation, including the /vanity_urls mechanism used to dynamically allow published AEM vanity URLs.
Adobe Experience League — Configure AEM Dispatcher
[6] Adobe Experience League — Sitemaps
Explains AEM sitemap generation and how Sling mappings are used to produce absolute sitemap URLs.
Adobe Experience League — Sitemaps
[7] Apache Sling API — ResourceResolver
API documentation for ResourceResolver.resolve() and ResourceResolver.map() and their relationship as incoming and reverse mapping operations.
Apache Sling — ResourceResolver API
Disclaimer
All domains, repository paths, page names, content structures and code examples in this article are fictional and are used exclusively to illustrate AEM URL-resolution concepts.
The exact mapping, Dispatcher, cache-invalidation and SEO configuration should always be designed and tested according to the architecture of each AEM as a Cloud Service implementation.