For AI agents: the complete documentation index is available at https://nhanbe.id.vn/llms.txt, the full documentation bundle is available at https://nhanbe.id.vn/llms-full.txt, and this page is available as Markdown at https://nhanbe.id.vn/aem/foundation/sling-model.md.

--**Flow Summary (HTL):*1. `data-sly-use` points to a class annotated with `@Model`. 2. Sling creates an instance and injects fields. 3. HTL use

Sling Model

Table of Contents

#Content
1What is a Sling Model, where it is used
2@Model + adaptables (Resource vs Request)
3Common injection annotations
4@Default, optional injection, @PostConstruct
5Interfaces + adapters
6JSON export (.model.json)
7Unit testing with AemContext
8Service users + repoinit (non-request contexts)
9Debugging (adapt returns null, empty fields)

1. Role

ConceptDescription
Sling ModelJava POJO adapted by Sling from a Resource or SlingHttpServletRequest, with fields injected automatically from JCR / request / OSGi.
Why neededDecouples content-reading logic from HTL; HTL only calls getters without repeating resource.getValueMap().get(...) across templates.
When requiredMost components with dialogs and logic should have a model (or delegate to another model).

Flow Summary (HTL):

  1. data-sly-use points to a class annotated with @Model.
  2. Sling creates an instance and injects fields.
  3. HTL uses ${model.property}.

2. Minimal Model

Java (core module, example com.mysite.core.models.TitleModel):

package com.mysite.core.models;

import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.Default;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;

@Model(adaptables = Resource.class)
public class TitleModel {

    @ValueMapValue
    @Default(values = "Default Title")
    private String title;

    @ValueMapValue
    @Default(values = "h2")
    private String headingLevel;

    public String getTitle() { return title; }
    public String getHeadingLevel() { return headingLevel; }
}

HTL:

<div data-sly-use.model="com.mysite.core.models.TitleModel">
    <h1 data-sly-element="${model.headingLevel}">${model.title}</h1>
</div>
AEM 6.5 NoteDetails
Packageorg.apache.sling.models.annotations.* (Sling Models API included with AEM).
data-sly-elementMust return a valid tag name (h1–h6); if authors enter invalid tags, consider validating in model or falling back to default.

3. adaptables: Resource vs SlingHttpServletRequest

adaptablesUsageTypical Injectors
Resource.classOnly needs properties / child nodes on the content resource.@ValueMapValue, @ChildResource, @ResourcePath, @OSGiService, @Self (Resource).
SlingHttpServletRequest.classNeeds request data: selectors, query parameters, RequestDispatcher, global HTL objects.All above + @RequestAttribute, @ScriptVariable, @Self (Request).
// Preferred when sufficient — simple, testable, runs outside HTTP requests (if Resource available)
@Model(adaptables = Resource.class)
public class SimpleModel { }

@Model(adaptables = SlingHttpServletRequest.class)
public class RequestAwareModel { }
Best Practice (6.5)Content
Prefer Resource.classFewer request dependencies, easy unit testing.
SlingHttpServletRequest when requiredPage, Style, HTL parameters, etc.

4. Injection Annotations Review

4.1. @ValueMapValue — Reading Properties

@ValueMapValue
private String title;

@ValueMapValue
private boolean featured;

@ValueMapValue
private java.util.Calendar publishDate;

@ValueMapValue(name = "jcr:title")
private String pageTitle;
Commonly Supported TypesNotes
String, numbers, boolean/Boolean, CalendarDefault name = field name; use name = "..." for namespaced properties (jcr:title, etc.).

4.2. @ChildResource — Child Nodes / Multifields

@ChildResource
private java.util.List<org.apache.sling.api.resource.Resource> links;

Multifield with Nested Model:

@Model(adaptables = Resource.class)
public class LinkItem {
    @ValueMapValue private String label;
    @ValueMapValue private String url;
    @ValueMapValue
    @Default(booleanValues = false)
    private boolean openInNewTab;
    // getters...
}

@Model(adaptables = Resource.class, defaultInjectionStrategy = org.apache.sling.models.annotations.DefaultInjectionStrategy.OPTIONAL)
public class NavigationModel {
    @ChildResource
    private java.util.List<LinkItem> links;
    public java.util.List<LinkItem> getLinks() {
        return links != null ? links : java.util.Collections.emptyList();
    }
}
AEM DialogNote
composite="{Boolean}true"Creates a child node per item — structure matches @ChildResource + child model.
Omitting compositeMay store as string/array — cannot map directly to List<LinkItem>.

4.3. @Self — The Adaptable Instance

@Model(adaptables = Resource.class)
public class PathModel {
    @Self
    private Resource resource;
    public String getPath() { return resource.getPath(); }
}

Request-based: @Self SlingHttpServletRequest request — used for query parameters, etc. Security: Request parameters are untrusted inputs; sanitize before using in queries, redirects, or authorizations.

4.4. @OSGiService

@Model(adaptables = Resource.class)
public class ArticleListModel {
    @org.apache.sling.models.annotations.injectorspecific.OSGiService
    com.day.cq.search.QueryBuilder queryBuilder;
    @Self
    private Resource resource;
}

Requires an active OSGi service; injected interface must match registered type.

4.5. @ScriptVariable (Request Adaptable Only)

@Model(adaptables = SlingHttpServletRequest.class)
public class PageHeaderModel {
    @org.apache.sling.models.annotations.injectorspecific.ScriptVariable
    private com.day.cq.wcm.api.Page currentPage;
}

4.6. @RequestAttribute — Parameters from HTL

<div data-sly-use.model="${'com.mysite.core.models.ListModel' @ maxItems=5}">
@Model(adaptables = SlingHttpServletRequest.class, defaultInjectionStrategy = org.apache.sling.models.annotations.DefaultInjectionStrategy.OPTIONAL)
public class ListModel {
    @org.apache.sling.models.annotations.injectorspecific.RequestAttribute
    @org.apache.sling.models.annotations.Default(intValues = 10)
    private int maxItems;
}

4.7. @ResourcePath

@org.apache.sling.models.annotations.injectorspecific.ResourcePath(path = "/content/mysite/en/jcr:content")
private Resource siteRoot;

4.8. @Via — Injecting from Resource when Adaptable is Request

@ValueMapValue
@org.apache.sling.models.annotations.Via("resource")
private String title;

5. @Default — Choosing the Right Attribute by Type

Field Type@Default AttributeExample
String / String[]values@Default(values = "x")
booleanbooleanValues@Default(booleanValues = false)
intintValues@Default(intValues = 10)
longlongValues
doubledoubleValues
Invalid Attribute UsageCompiles successfully but defaults fail at runtime.

6. @PostConstruct — Post-Injection Initialization

AEM 6.5: uses javax.annotation.PostConstruct (Java 8 and 11 on AEM 6.5 stack).

import javax.annotation.PostConstruct;

@Model(adaptables = Resource.class, defaultInjectionStrategy = org.apache.sling.models.annotations.DefaultInjectionStrategy.OPTIONAL)
public class ArticleModel {
    @ValueMapValue private String text;
    private int readingTime;

    @PostConstruct
    protected void init() {
        if (text != null) {
            int wordCount = text.split("\\s+").length;
            readingTime = Math.max(1, wordCount / 200);
        }
    }
    public int getReadingTime() { return readingTime; }
}
Common Use CasesExample
Derived valuesreadingTime, formatted strings.
Validation / normalizationTrimming strings, complex fallback logic.

7. Optional Injection

ApproachDescription
Per-field@ValueMapValue(injectionStrategy = org.apache.sling.models.annotations.InjectionStrategy.OPTIONAL)
Model-wide (Recommended)defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL on @Model
6.5 PracticeRecommendation
Authors leave fields blankOPTIONAL + @Default reduces adaptTo(...) == null occurrences.
Mandatory business fieldsValidate inside @PostConstruct or HTL data-sly-test.

8. Interfaces + adapters (Clean API for HTL & Testing)

public interface Hero {
    String getHeading();
    String getSubheading();
    String getImagePath();
}
@Model(
    adaptables = Resource.class,
    adapters = Hero.class,
    defaultInjectionStrategy = org.apache.sling.models.annotations.DefaultInjectionStrategy.OPTIONAL
)
public class HeroImpl implements Hero {
    @ValueMapValue private String heading;
    @ValueMapValue private String subheading;
    @ValueMapValue(name = "fileReference") private String imagePath;
    @Override public String getHeading() { return heading; }
    @Override public String getSubheading() { return subheading; }
    @Override public String getImagePath() { return imagePath; }
}

HTL using interface:

<div data-sly-use.hero="com.mysite.core.models.Hero">
    <h1>${hero.heading}</h1>
</div>
Advantages
TestingEasy to mock Hero.
Swapping implementationsHTL remains untouched.

9. JSON Export (Headless / SPA) — .model.json

Requirements Overview:

ComponentDescription
adaptersIncludes com.adobe.cq.export.json.ComponentExporter (AEM)
resourceTypeMatches component sling:resourceType
@ExporterTypically uses name = "jackson", extensions = "json", selector = "model" — bundle requires compatible Jackson exporter dependency (pre-configured in AEM archetypes).
JSON ContentExported fields depend on model getters, com.adobe.cq.export.json version, and serializer settings — align with project core/pom.xml.

Skeleton Example:

@Model(
    adaptables = Resource.class,
    adapters = { Hero.class, com.adobe.cq.export.json.ComponentExporter.class },
    resourceType = "mysite/components/hero",
    defaultInjectionStrategy = org.apache.sling.models.annotations.DefaultInjectionStrategy.OPTIONAL
)
@org.apache.sling.models.annotations.exporter.Exporter(
    name = "jackson",
    extensions = "json",
    selector = "model"
)
public class HeroImpl implements Hero, com.adobe.cq.export.json.ComponentExporter {

    @ValueMapValue
    private String heading;

    @Override
    public String getExportedType() {
        return "mysite/components/hero";
    }

    public String getHeading() { return heading; }
}
AEM 6.5 Note
URLCall on component instance resource, e.g.: .../jcr:content/.../hero.model.json.
Quick CheckTest on Author/Publish instance; if 404, check resourceType and com.adobe.cq.export.json package imports.

10. Unit Testing (wcm.io Aem Mocks, JUnit 5)

Element
Dependencyio.wcm:io.wcm.testing.aem-mock.junit5 (project BOM).
PatternAemContext + context.create().resource(...) + resource.adaptTo(InterfaceOrModel.class).
import io.wcm.testing.aem.junit5.AemContext;
import io.wcm.testing.aem.junit5.AemContextExtension;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.apache.sling.api.resource.Resource;

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

@ExtendWith(AemContextExtension.class)
class HeroImplTest {
    private final AemContext context = new AemContext();

    @Test
    void headingFromValueMap() {
        context.create().resource("/content/test",
            "sling:resourceType", "mysite/components/hero",
            "heading", "Hello",
            "subheading", "AEM 6.5");
        Resource r = context.resourceResolver().getResource("/content/test");
        Hero h = r.adaptTo(Hero.class);
        assertEquals("Hello", h.getHeading());
    }
}

11. Quick Troubleshooting

SymptomResolution
adaptTo(Model) returns nullIncorrect adaptables; missing defaultInjectionStrategy + missing property; OSGi model registration error.
Field is nullMatch JCR property name with annotation name; property unsaved after dialog save.
List multifield emptyVerify composite structure + child node path.
@OSGiService is null / model failsOSGi service inactive; interface mismatch.
Works locally, fails on deployMissing core bundle package, or missing OSGi configuration.

12. Non-Request Contexts: Service Users + Repoinit (On-Prem 6.5)

ProblemSolution
Schedulers / events / workflows lack requestsUse ResourceResolverFactory.getServiceResourceResolver(...) with mapped subservice.
Creating users & ACLs programmaticallyUse RepositoryInitializer (repoinit) in ui.config — versioned in source control.
Do NOT doOpen admin sessions in app code.

Repoinit Example Configuration:

ui.config/.../org.apache.sling.jcr.repoinit.RepositoryInitializer~mysite.cfg.json

{
  "scripts": [
    "create service user mysite-reader",
    "set ACL for mysite-reader",
    "  allow jcr:read on /content/mysite",
    "end"
  ]
}

Service User Mapping:

org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended~mysite.cfg.json

{
  "user.mapping": [
    "com.mysite.core:mysite-reader=mysite-reader"
  ]
}

Code:

import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
// ...
Map<String, Object> auth = Map.of(ResourceResolverFactory.SUBSERVICE, "mysite-reader");
try (ResourceResolver rr = resolverFactory.getServiceResourceResolver(auth)) {
    // use rr, always use try-with-resources
}
On-Prem vs CloudNote
AEM 6.5Can create users manually on dev; production should use repoinit + ACL as code.
AEMaaCSCannot create users via CRXDE on prod; mandatory configuration approach.

13. Security Summary

Principle
Request parametersValidate / whitelist inputs.
ResolversClose custom ResourceResolver instances (try-with-resources).
PermissionsUse minimum service user permissions; avoid admin.
LoggingAvoid logging sensitive data / PII.

14. Summary

  • Sling Model = Java class annotated with @Model for Sling to inject values from JCR/request/OSGi, invoked in HTL via data-sly-use.
  • Prefer Resource.class when sufficient; use SlingHttpServletRequest when global HTL objects or request data are needed.
  • Use DefaultInjectionStrategy.OPTIONAL + @Default for stable authoring environments.
  • Use Interfaces + adapters to separate API definitions and simplify testing.
  • .model.json: Requires ComponentExporter + @Exporter + resourceType.
  • Outside HTTP requests: Use service user + repoinit + mapping; avoid admin sessions.