4.1 Sling Models Framework, Injectors & Model Exporter

Key Takeaways

  • Sling Models are annotation-driven POJOs or interfaces that map JCR resources or HTTP requests to Java objects, eliminating boilerplate JCR API calls and manual type casting.
  • The choice of adaptables (Resource.class vs SlingHttpServletRequest.class) dictates injector scope: request-adapted models access request attributes, query parameters, and scripting variables, whereas resource-adapted models are lighter and decoupled from the HTTP layer.
  • Specialized injector annotations (@ValueMapValue, @ChildResource, @Self, @OSGiService, @RequestAttribute, @ScriptVariable) replace generic @Inject to provide deterministic, compile-safe, and high-performance injection.
  • Setting defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL on @Model prevents complete model adaptation failure when authored properties are missing in the JCR repository.
  • The Sling Model Exporter framework utilizes Jackson serialization (@Exporter(name = "jackson", extensions = "json")) to expose structured content via the .model.json selector for Headless CMS and SPA Editor integration.
Last updated: September 2026

4.1 Sling Models Framework, Injectors & Model Exporter

Exam Focus: Apache Sling Models represent the standard backend programming model for Adobe Experience Manager Sites. The AD0-E128 exam tests your mastery of @Model configuration parameters (adaptables, adapters, defaultInjectionStrategy), specialized injector annotations (@ValueMapValue, @ChildResource, @Self, @OSGiService, @RequestAttribute, @ScriptVariable), post-construction lifecycle management (@PostConstruct), the Sling Model Exporter (@Exporter with Jackson for .model.json), and comprehensive unit testing using AEM Mocks (AemContext).


Architectural Philosophy of Apache Sling Models

Historically, AEM developers extracted content from the Java Content Repository (JCR) using low-level JCR APIs (javax.jcr.Node, javax.jcr.Property) or Apache Sling ValueMap decorators in JSP or Java Use-API classes (WCMUsePojo). This legacy paradigm forced developers to write repetitive, error-prone boilerplate:

  • Explicit type casting and null checks (if (properties.get("title", String.class) != null) ...).
  • Manual traversal of child nodes and sub-resources.
  • Tightly coupled scripting contexts and unhandled repository exceptions.
  • Inability to unit test presentation logic without running a full OSGi integration container.

Introduced to solve these challenges, Apache Sling Models is an annotation-driven Object-to-Content Mapping (OCM) framework. Sling Models are standard Plain Old Java Objects (POJOs) or interfaces that bind repository resources or HTTP requests to strongly typed Java instances via reflection and runtime injectors. By offloading resource resolution, type coercion, and service lookup to the framework, developers achieve clean separation of concerns, testability, and enterprise-grade maintainability.


The @Model Annotation & Core Parameters

Every Sling Model class or interface must be annotated with @Model (org.apache.sling.models.annotations.Model). The annotation accepts several critical parameters that define how and where the model can be instantiated.

1. adaptables: Resource.class vs SlingHttpServletRequest.class

The adaptables parameter defines what context object can be adapted into the model:

  • adaptables = Resource.class: The model adapts directly from an org.apache.sling.api.resource.Resource.
  • adaptables = SlingHttpServletRequest.class: The model adapts from an org.apache.sling.api.SlingHttpServletRequest.
Architectural Attributeadaptables = Resource.classadaptables = SlingHttpServletRequest.class
Context ScopeScoped strictly to the JCR resource node.Scoped to the active HTTP request context.
Available Injectors@ValueMapValue, @ChildResource, @Self (Resource), @OSGiService.All resource injectors plus @RequestAttribute, @ScriptVariable, request parameters, headers, cookies, selectors, and suffix.
HTL Usage<div data-sly-use.m="MyModel"> (adapts current resource).<div data-sly-use.m="MyModel"> (adapts current request).
Off-Request AdaptabilityCan be adapted anywhere: Sling Jobs, OSGi Event Listeners, Workflow Process steps, scheduled tasks.Cannot be adapted outside an active HTTP request (will return null).
Unit Testing OverheadMinimal; requires only mock resources in AemContext.Requires mock HTTP request, response, attributes, and request parameters.
Architectural RecommendationDefault choice: Use unless HTTP request data or HTL arguments are explicitly needed.Required when reading HTL template parameters, query parameters, or scripting objects like currentPage.

2. adapters & Interface-Driven Design

Enterprise AEM development mandates separating public interface contracts from internal implementation classes:

package com.myproject.core.models;

public interface Teaser {
    String getTitle();
    String getDescription();
    String getLinkUrl();
    boolean isActionEnabled();
}

The implementation class implements the interface and declares it in the adapters array:

package com.myproject.core.models.impl;

import com.myproject.core.models.Teaser;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.DefaultInjectionStrategy;

@Model(
    adaptables = Resource.class,
    adapters = Teaser.class,
    resourceType = "myproject/components/content/teaser",
    defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
public class TeaserImpl implements Teaser {
    // fields and getters...
}

HTL scripts reference the public interface: data-sly-use.teaser="com.myproject.core.models.Teaser". The Sling Models runtime discovers TeaserImpl registered as an adapter for Teaser.class and instantiates the implementation transparently.

3. defaultInjectionStrategy: Handling Null Properties

By default, Sling Models operate under DefaultInjectionStrategy.REQUIRED. If any field in the class fails to inject (because the property does not exist on the JCR node and no @Default is defined), the framework considers the adaptation an error:

Critical Rule: Under DefaultInjectionStrategy.REQUIRED, if a single non-optional injection fails, the entire model adaptation fails and returns null.

In content authoring, editors frequently leave optional dialog fields unauthored. If a model containing ten fields fails completely because one optional text field is empty, the entire component disappears from the page.

Therefore, the enterprise standard is to declare:

defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL

When OPTIONAL is set at the class level, missing properties inject as null (or default primitive values: 0, false), allowing the model to adapt successfully. Individual fields that are strictly mandatory can be marked with the @Required annotation.


Dedicated Injector Annotations

While early versions of Sling Models used the generic @Inject annotation, modern AEM development should prefer injector-specific annotations from org.apache.sling.models.annotations.injectorspecific.*.

Generic @Inject makes the injection source less explicit and can require trying multiple injectors. Injector-specific annotations document intent, reduce ambiguity, and produce clearer diagnostics.

1. @ValueMapValue: Injecting Node Properties

Injects properties from the adaptable's ValueMap:

@ValueMapValue
private String title;

// Inject property with custom name and fallback
@ValueMapValue(name = "jcr:title")
@Default(values = "Default Title")
private String pageTitle;

@ValueMapValue
@Named("cq:lastModifiedBy")
private String modifiedBy;

2. @ChildResource: Injecting Sub-Nodes and Collections

Injects child nodes as Resource, ValueMap, or another nested Sling Model. This is the cornerstone of authoring multifields and composite components:

// Injects a single child resource adapted to a child model
@ChildResource(name = "image")
private ImageModel image;

// Injects child nodes under 'items' as a list of LinkItem models (Multifield pattern)
@ChildResource(name = "items")
private List<LinkItem> items;

3. @Self: Injecting the Adaptable

Injects the adaptable itself into the model or adapts the current adaptable into another object:

// Injecting the underlying Resource in a Resource-adapted model
@Self
private Resource resource;

// In a SlingHttpServletRequest model, adapting the request's resource to Core Component Teaser
@Self
@Via(type = ResourceSuperType.class)
private com.adobe.cq.wcm.core.components.models.Teaser coreTeaser;

4. @OSGiService: Injecting OSGi Services

Injects active OSGi services into the model. Supports optional LDAP target filters:

@OSGiService
private ModelFactory modelFactory;

@OSGiService(filter = "(service.ranking>=500)")
private PaymentGatewayService paymentService;

5. @RequestAttribute: Capturing HTL Parameters

When adapting from SlingHttpServletRequest.class, @RequestAttribute captures parameters passed from HTL data-sly-use or data-sly-template:

<!-- HTL Markup passing dynamic parameters -->
<div data-sly-use.card="${'com.myproject.core.models.Card' @ cardTheme='dark', maxItems=5}">
    <p class="theme-${card.cardTheme}">Displaying ${card.maxItems} items</p>
</div>
@Model(adaptables = SlingHttpServletRequest.class, adapters = Card.class)
public class CardImpl implements Card {
    @RequestAttribute(name = "cardTheme")
    @Default(values = "light")
    private String cardTheme;

    @RequestAttribute(name = "maxItems")
    @Default(intValues = 3)
    private int maxItems;
}

6. @ScriptVariable: Injecting Scripting Objects

Injects standard AEM/Sling scripting objects into request-adapted models:

@ScriptVariable
private Page currentPage;

@ScriptVariable
private ResourceResolver resourceResolver;

@ScriptVariable
private Style currentStyle;

@ScriptVariable
private PageManager pageManager;

Post-Construction Lifecycle: @PostConstruct

In many components, raw injected values require post-processing: assembling complex URLs, computing date formats, validating business rules, or loading secondary resources.

The @PostConstruct annotation (javax.annotation.PostConstruct) designates a method executed immediately after all field injections are complete:

@PostConstruct
protected void init() {
    if (StringUtils.isNotBlank(linkUrl)) {
        // Resolve vanity URLs or external links
        this.formattedUrl = resourceResolver.map(linkUrl);
    }
    this.hasValidData = StringUtils.isNotBlank(this.title) && this.formattedUrl != null;
}

Exception Handling & Failure Rules in @PostConstruct

Understanding @PostConstruct error mechanics is critical for the exam:

  1. Uncaught Exceptions Abort Adaptation: If an unhandled runtime exception (NullPointerException, IllegalStateException) is thrown inside @PostConstruct, the Sling Models runtime aborts adaptation and returns null.
  2. Checked Exceptions Forbidden: The @PostConstruct method cannot declare checked exceptions in its method signature. Any checked exceptions from underlying APIs (e.g., RepositoryException) must be caught internally:
@PostConstruct
protected void init() {
    try {
        performComplexInitialization();
    } catch (Exception e) {
        LOG.error("Failed to initialize model for resource: {}", resource.getPath(), e);
        // Set safe fallback state rather than rethrowing
        this.items = Collections.emptyList();
    }
}

Sling Model Exporter & Headless Delivery (.model.json)

The Sling Model Exporter framework enables Sling Models to serialize directly into JSON (or XML) representations. This is the foundational technology powering the AEM SPA Editor (React/Angular), Headless CMS delivery, and content synchronization with mobile applications.

1. The @Exporter Annotation

To expose a model through the exporter, annotate the class with @Exporter:

@Model(
    adaptables = {SlingHttpServletRequest.class, Resource.class},
    adapters = {Teaser.class, ComponentExporter.class},
    resourceType = "myproject/components/content/teaser",
    defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
@Exporter(name = "jackson", extensions = "json", options = {
    @ExporterOption(name = "SerializationFeature.WRITE_DATES_AS_TIMESTAMPS", value = "false")
})
public class TeaserImpl implements Teaser, ComponentExporter {

    @ValueMapValue
    private String title;

    @ValueMapValue
    private String description;

    @ValueMapValue
    @JsonIgnore
    private String internalTrackingKey; // Excluded from exported JSON

    @JsonProperty("heading")
    @Override
    public String getTitle() {
        return title;
    }

    @Override
    public String getExportedType() {
        return "myproject/components/content/teaser";
    }
}

2. Exporter Annotations & Capabilities

  • @JsonProperty("key"): Overrides the JSON property key in the serialized payload.
  • @JsonIgnore: Strictly suppresses sensitive, internal, or circular references from appearing in the JSON output.
  • @JsonInclude(JsonInclude.Include.NON_NULL): Suppresses null properties to keep the JSON payload lightweight.
  • ComponentExporter: Interface required by the AEM SPA Editor. Implementing getExportedType() returns the sling:resourceType so front-end SPAs know which JavaScript component to mount.

3. Exposing Models via .model.json

When @Exporter(name = "jackson", extensions = "json") is registered with a resourceType, Apache Sling's Model Exporter Servlet automatically intercepts requests matching: https://www.example.com/content/myproject/us/en/jcr:content/root/teaser.model.json It executes Jackson serialization on the adapted model and streams the JSON response with Content-Type: application/json.


Unit Testing Sling Models with AEM Mocks (AemContext)

Unit testing Sling Models without starting a live AEM instance is achieved using the open-source io.wcm.testing.aem-mock (AEM Mocks) library and JUnit 5.

AemContext simulates an in-memory JCR repository (Apache Sling ResourceResolver Mock or Oak Mock), OSGi bundle context, and Sling HTTP request/response pipeline:

import io.wcm.testing.mock.aem.junit5.AemContext;
import io.wcm.testing.mock.aem.junit5.AemContextExtension;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

import static org.junit.jupiter.api.Assertions.*;

@ExtendWith(AemContextExtension.class)
class TeaserModelTest {

    private final AemContext context = new AemContext();

    @BeforeEach
    void setUp() {
        // Register Sling Model classes
        context.addModelsForClasses(TeaserImpl.class);

        // Load mock JSON content into simulated JCR
        context.load().json("/com/myproject/core/models/TeaserTest.json", "/content/teaser");
    }

    @Test
    void testTeaserModelAdaptation() {
        // Set current resource context
        context.currentResource("/content/teaser");

        // Adapt resource to model interface
        Teaser teaser = context.currentResource().adaptTo(Teaser.class);

        assertNotNull(teaser, "Teaser model must adapt successfully");
        assertEquals("Summer Campaign", teaser.getTitle());
        assertTrue(teaser.isActionEnabled());
    }
}

This testing pattern runs in milliseconds within standard Maven mvn test phases, enforcing continuous code quality gates in Cloud Manager CI/CD pipelines.

Loading diagram...
Sling Model Adaptation and Injection Lifecycle
Test Your Knowledge

Why would an AEM developer choose adaptables = SlingHttpServletRequest.class instead of adaptables = Resource.class when declaring a Sling Model?

A
B
C
D
Test Your Knowledge

In Apache Sling Models, what occurs if an authored JCR property is missing from the repository, and the model class does NOT specify defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL?

A
B
C
D
Test Your Knowledge

A developer needs to expose an existing Sling Model's data as a JSON payload for consumption by a Single Page Application (SPA) using the .model.json URL selector. Additionally, an internal tracking token field in the model must be excluded from this JSON output. Which combination of annotations correctly satisfies this requirement?

A
B
C
D
Test Your Knowledge

In the Sling Models lifecycle, when is a method annotated with @PostConstruct executed, and what occurs if an unhandled runtime exception is thrown during its execution?

A
B
C
D