AEM: Sling Framework
Understanding Sling Resource Resolution, Sling Models, Exporters, and Sling Jobs
The Apache Sling framework forms the core of request handling in Adobe Experience Manager (AEM). It introduces a powerful, resourceโcentric approach for resolving requests, mapping URL structures, building models, exporting content as JSON, and processing asynchronous tasks through jobs.
This article breaks down four major Sling concepts:
- Sling Resource Resolution Framework
- Sling Models & Adaptation
- Sling Exporter Framework
- Sling Jobs & Job Processing
Sling Resource Resolution Framework
In Sling, everything is a resource. When a request comes in, Sling evaluates the URL and maps it to the best possible matching resource inside the repository. To do this, Sling decomposes the URL into distinct logical parts.
URL Decomposition
Consider the URL: /a/b.s1.s2.html/c/d.s.txt?q=x
This decomposes into:
| Part | Value | Description |
| Resource Path | /a/b | The longest existing path inside the repository |
| Selectors | s1.s2 | Parts after the first dot but before the final dot before the extension |
| Extension | html | The part after the last dot before the next slash |
| Suffix | /c/d.s.txt | Everything after extension until end of the URL |
| Query Parameter | q=x | Standard query string |
Rules of Decomposition
1. Resource Path
- Longest path inside the repository.
- Must exist; otherwise Sling falls back to resource resolution rules.
2. Selectors
Selectors are optional decorations added to request processing.
Rules:
- They exist only if the first character after the resource path is a dot.
- The segment between this first dot and the last dot before the extension is the selectors string.
- If there is only one dot before the extension, no selectors exist.
Example:
/content/page.print.a4.html
Selectors = print, a4
Extension = html
Selectors are often used for:
- Rendering variants (mobile/desktop)
- Export modes (print, data)
- Data views (summary/full)
3. Extension
- Follows the last dot before a slash or end of URL.
- Defines the response type (e.g., html, json, xml).
4. Suffix
- Begins with / after selectors and extension.
- Often used to pass a path-like value to servlets/components.
Example for URL Decomposition & Request Handling (Resource Path, Selectors, Extension, Suffix)
Servlet that reads selectors, extension, and suffix
@Component(service = Servlet.class, property = {
ServletResolverConstants.SLING_SERVLET_RESOURCE_TYPES + "=stacknowledge/components/page",
ServletResolverConstants.SLING_SERVLET_SELECTORS + "=s1",
ServletResolverConstants.SLING_SERVLET_EXTENSIONS + "=html",
ServletResolverConstants.SLING_SERVLET_METHODS + "=GET"
})
public class UrlDecompositionServlet extends SlingSafeMethodsServlet {
@Override
protected void doGet(final SlingHttpServletRequest request,
final SlingHttpServletResponse response) throws ServletException, IOException {
// Resource path (resolved resource)
Resource resource = request.getResource();
String resourcePath = resource.getPath();
// Selectors (array)
String[] selectors = request.getRequestPathInfo().getSelectors();
// Extension
String extension = request.getRequestPathInfo().getExtension();
// Suffix (as string and resource)
String suffix = request.getRequestPathInfo().getSuffix();
Resource suffixResource = request.getRequestPathInfo().getSuffixResource();
response.setContentType("text/plain");
response.getWriter().printf(
"Resource path: %s%nSelectors: %s%nextension: %s%nsuffix: %s%nsuffixResource: %s%n",
resourcePath,
Arrays.toString(selectors),
extension,
suffix,
suffixResource != null ? suffixResource.getPath() : "null"
);
}
}
How it resolves for: /a/b.s1.s2.html/c/d.s.txt?q=x
- getResource().getPath() โ /a/b
- getRequestPathInfo().getSelectors() โ [“s1″,”s2”]
- getRequestPathInfo().getExtension() โ html
- getRequestPathInfo().getSuffix() โ /c/d.s.txt
Sling Models
Sling Models offer a clean, annotationโdriven way to map Sling resources or requests to Java POJOs.
Adapter Concept
Sling Models rely on the Sling Adapter Framework. Adapting means: โTake one Sling object, convert it into another representation.โ
Common adaptables:
- Resource.class
- SlingHttpServletRequest.class
Adapting from Resource
@Model(adaptables = Resource.class, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ProductCardModel {
@ValueMapValue @Default(values = "My Title")
private String title;
@ValueMapValue @Default(values = "/content/dam/stacknowledge/pic.jpg")
private String image;
@ValueMapValue @Default(booleanValues = false)
private boolean featured;
@ValueMapValue @Default(intValues = 3)
private int maxItems;
public String getTitle() { return title; }
public String getImage() { return image; }
public boolean isFeatured() { return featured; }
public int getMaxItems() { return maxItems; }
}
//Usage
ProductCardModel model = resource.adaptTo(ProductCardModel.class);
- Resource โ Model adaption internally uses ValueMap.
- Values injected into fields come from CRX properties.
- Good for simple data-backed components.
Adapting from SlingHttpServletRequest
@Model(adaptables = SlingHttpServletRequest.class)
public class ProductTile {
@Inject @Self private SlingHttpServletRequest request;
@Inject @Reference private HybrisConnectionManager connectionManager;
@Inject @Via("resource")
@Default(intValues = 3)
private int featuresShown;
}
Why adapt from request?
- Gives access to request parameters, selectors, suffix, etc.
- Useful when business logic depends on runtime conditions.
@Self, @Via, @Default, and @Reference simplify dependency retrieval.
Alternative: Model Factory
@Reference
private ModelFactory modelFactory;
...
MyModel model = modelFactory.createModel(object, MyModel.class);
Used when:
- The adaptable object is dynamic.
- You want more control over model creation.
Sling Exporter
Sling Exporter allows converting Sling Models into structured JSON output (usually for SPA frameworks such as React/Vue).
@Model(adaptables = SlingHttpServletRequest.class,
adapters = { MyComponentModel.class, ContainerExporter.class },
resourceType = {"/apps/stacknowledge/myComponent", "/apps/stacknowledge/myComponent2"},
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
@Exporter(name = "jackson", extensions = "json")
public class MyComponentModel implements ContainerExporter {
...
}
Key Concepts
- @Exporter(name=”jackson”) โ Uses Jackson for JSON generation.
- extensions = “json” โ Access via .model.json.
- Exporters automatically serialize all getters.
ComponentExporter vs ContainerExporter
| Interface | Responsibilities |
| ComponentExporter | Provides component type via getExportedType() |
| ContainerExporter | Extends ComponentExporter, adds:โข getExportedItems()โข getExportedItemsOrder() |
Override these to customize JSON output for SPA Components.
Sling Jobs
Sling Jobs provide asynchronous processing capabilities, ideal for tasks such as:
- Data synchronization
- Email sending
- Re-indexing
- Heavy computation
Job Structure
Every job has:
- Job Topic โ identifies job type.
- Payload โ keyโvalue map of serializable data.
Creating a Job
Traditional approach
jobManager.addJob("stacknowledge/jobs/topic", props);
Using JobBuilder (recommended)
jobManager.createJob("stacknowledge/jobs/topic")
.properties(props)
.add();
add() must be the final method โ it queues the job.
@Component(service = JobStarter.class)
public class JobStarter {
public static final String JOB_TOPIC = "stacknowledge/jobs/topic";
@Reference
private JobManager jobManager;
public void start(String path, String user) {
Map<String, Object> props = new HashMap<>();
props.put("path", path);
props.put("initiatedBy", user);
props.put("priority", "normal");
// Recommended: JobBuilder
JobBuilder builder = jobManager.createJob(JOB_TOPIC).properties(props);
builder.add(); // enqueue
}
}
JobConsumer
A job consumer listens for specific topics.
@Component(service = JobConsumer.class, property = {
JobConsumer.PROPERTY_TOPICS + "=" + JobStarter.JOB_TOPIC
})
public class MyJobConsumer implements JobConsumer {
@Override
public JobResult process(Job job) {
String path = (String) job.getProperty("path");
String user = (String) job.getProperty("initiatedBy");
try {
// Execute business logic
// ...
return JobResult.OK;
} catch (CustomException e) {
// Retryable failure
return JobResult.FAILED;
} catch (Exception e) {
// Permanent failure
return JobResult.CANCEL;
}
}
private static class CustomException extends RuntimeException {}
}
Possible Results
- JobResult.OK โ Success
- JobResult.FAILED โ Retry allowed
- JobResult.CANCEL โ Permanent failure
JobExecutor
@Component(service = JobExecutor.class,
property = { JobExecutor.PROPERTY_TOPICS + "=" + JobStarter.JOB_TOPIC})
public class MyJobExecutor implements JobExecutor {
@Override
public boolean process(final Job job) {
String path = (String) job.getProperty("path");
job.getProgressLog().log("Starting for: " + path);
try {
// Step 1
job.getProgressLog().log("Fetching remote data...");
// ...
// Step 2
job.getProgressLog().log("Applying transformations...");
// ...
// Step 3
job.getProgressLog().log("Persisting results...");
// ...
job.getProgressLog().log("Completed.");
return true; // success
} catch (Exception e) {
job.getProgressLog().log("Error occurred: " + e.getMessage());
return false; // triggers retry based on configuration
}
}
}
Use JobExecutor when:
- You need progress trackers.
- You want to attach more detailed metadata.
- You require advanced error reporting.
Minimal pom.xml dependencies
Version numbers depend on your AEM/Sling baseline. Use the AEM Project Archetype to bootstrap a correct set.
<dependencies>
<!-- Sling Models -->
<dependency>
<groupId>org.apache.sling</groupId>
<artifactId>org.apache.sling.models.api</artifactId>
<version>1.5.0</version>
<scope>provided</scope>
</dependency>
<!-- Sling API -->
<dependency>
<groupId>org.apache.sling</groupId>
<artifactId>org.apache.sling.api</artifactId>
<version>2.25.0</version>
<scope>provided</scope>
</dependency>
<!-- OSGi Annotations -->
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.service.component.annotations</artifactId>
<version>1.4.0</version>
<scope>provided</scope>
</dependency>
<!-- Sling Event / Jobs -->
<dependency>
<groupId>org.apache.sling</groupId>
<artifactId>org.apache.sling.event</artifactId>
<version>4.3.8</version>
<scope>provided</scope>
</dependency>
<!-- AEM SPA Exporter APIs (if using ContainerExporter) -->
<dependency>
<groupId>com.adobe.cq</groupId>
<artifactId>core.wcm.components.core</artifactId>
<version>2.21.2</version>
<scope>provided</scope>
</dependency>
</dependencies>



Post Comment