Sling Model Complete Notes
A complete guide to Sling Models in AEM, covering adaptables, annotations, resource mapping, and best practices for component models.
Content Objective
- What is a Sling Model?
- How Sling Models work in AEM
- Adaptables and model syntax
- Common Sling Model annotations
- Best practices for AEM Sling Models
What is the Sling Model?
The Sling Model is a lightweight framework inside AEM that maps resource objects and request data to Java classes. It is part of AEM’s architecture for connecting repository content to HTL views using OSGi-enabled Java models.
- Sling Models simplify the mapping of component properties to Java fields.
- They are typically implemented as OSGi services and adapted from
ResourceorSlingHttpServletRequest. - They keep content retrieval and business logic separate from the HTL template.
Features of Sling Models
- Annotations-Driven: Uses annotations like
@Model,@Inject, and@ValueMapValueto map AEM properties automatically. - Built-in Dependency Injection: Injects properties, services, resources, and request objects directly from the Sling context.
- Versatile Adaptability: Works with multiple adaptables such as
ResourceandSlingHttpServletRequest. - Modular Structure: Keeps content access and logic outside the HTL view for cleaner, testable code.
Sling Model Workflow
A Sling Model typically follows this workflow:
- A page author adds a component to a page.
- AEM resolves the component’s
sling:resourceTypeto a path under/apps. - AEM loads the HTL script for that component.
- The HTL references a Sling Model class using
data-sly-use. - AEM instantiates the Sling Model and injects required values.
- The returned model data is rendered by the HTL.
For example, a component may be resolved from:
/content/aem-debugcode/us/en/test-page/jcr:content/root/container/customcomponent
If the component’s resource type points to:
/apps/aem-debugcode/components/custom-component
Then HTL may load the model like this:
<sly data-sly-use.customComponent="com.debug.code.core.models.CustomComponent" />
After this, AEM binds the HTL file to the CustomComponent Sling Model and executes its methods to render the final HTML output.
How Many Ways Can a Sling Model Adapt?
A Sling Model can be adapted from two common adaptables:
Resource.classSlingHttpServletRequest.class
Syntax example
@Model(
adaptables = {Resource.class, SlingHttpServletRequest.class},
adapters = {Button.class},
resourceType = {ButtonImpl.RESOURCE_TYPE},
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
public class ButtonImpl implements Button {
// logic
}
Common Sling Model Annotations
@Model
This annotation marks the class as a Sling Model. It defines the adaptables, adapters, resource type, and default injection strategy.
adaptables
The adaptables attribute defines the source objects from which the model can be adapted.
Resource.classis used when the model is based on repository content.SlingHttpServletRequest.classis used when the model needs request-specific information.
adapters
The adapters attribute specifies the interface or class that other code can adapt to from this model.
resourceType
The resourceType attribute declares the component resource types supported by this model.
defaultInjectionStrategy
This controls how missing values are handled during injection.
DefaultInjectionStrategy.OPTIONALsets missing values tonull.DefaultInjectionStrategy.REQUIREDthrows an error if a required value is missing.
Example:
@Model(
adaptables = Resource.class,
adapters = CustomComponent.class,
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
What are Annotations in Sling Models?
Annotations in Sling Models map properties, resources, services, and request values to Java fields. They make it easier to read repository content and avoid manual lookups.
Types of Annotations
@Inject
@Inject is a generic injection annotation used for various contexts in Sling Models.
- It is flexible but less explicit than specialized annotations.
- By default, injected fields are optional and can become
nullif values are absent.
Example:
@Inject
private String firstName;
@ValueMapValue
@ValueMapValue injects properties directly from the current resource’s ValueMap.
- It is ideal for mapping resource properties to model fields.
- It supports type conversion and default values.
Example:
@ValueMapValue
private String firstName;
@Named
@Named is useful when the model field name differs from the repository property name.
Example:
@ValueMapValue
@Named("jcr:lastModifiedBy")
private String createdBy;
@Default
@Default provides fallback values when injection is missing.
Example:
@ValueMapValue
@Default(values = "Default Title Field")
private String titleField;
@ValueMapValue
@Default(intValues = 0)
private int count;
@Self
@Self injects the adaptable object itself into the model. This is useful when you need direct access to the resource or request.
Example:
@Self
private Resource resource;
Example 1: Accessing the HTTP request
@Model(adaptables = SlingHttpServletRequest.class)
public class RequestModel {
@Self
private SlingHttpServletRequest request;
public String getRequestPath() {
return request.getRequestURI();
}
}
Example 2: Working with a resource
@Model(adaptables = Resource.class)
public class CustomModel {
@Self
private Resource resource;
public boolean hasChildNodes() {
return resource.hasChildren();
}
}
@Via
@Via allows the model to inject values from a related resource or alternative path.
Common use cases:
- Accessing a child resource
- Accessing a parent resource
- Injecting values from a nested model
Example structure:
/content/myproject/jcr:content
+ myComponent
+ buttondetails
- titleField: "Hello"
- descriptionField: "This is a descriptionField."
Example:
@Model(
adaptables = Resource.class,
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
public class MyComponentModel {
@ValueMapValue
@Via("buttondetails")
private String titleField;
@ValueMapValue
@Via("buttondetails")
private String descriptionField;
}
Example using parent-child injection:
@Model(
adaptables = Resource.class,
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
public class ParentModel {
@ValueMapValue
private String titleField;
public String getTitleField() {
return titleField;
}
}
@Model(
adaptables = Resource.class,
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
public class ChildModel {
@Self
@Via("parentResource")
private ParentModel parentModel;
public String getTitleFromParent() {
return parentModel.getTitleField();
}
}
@PostConstruct
@PostConstruct marks a method that runs after all injections are complete.
It is ideal for initialization logic and default-value handling.
Example:
@Model(
adaptables = Resource.class,
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
public class CustomModel {
@ValueMapValue
private String titleField;
@ValueMapValue
private String descriptionField;
@PostConstruct
protected void init() {
if (titleField == null || titleField.isEmpty()) {
titleField = "Default Title Field";
}
if (descriptionField == null || descriptionField.isEmpty()) {
descriptionField = "Default Description Field";
}
}
public String getTitleField() {
return titleField;
}
public String getDescriptionField() {
return descriptionField;
}
}
@OSGiService
@OSGiService injects an OSGi service directly into the Sling Model.
Example:
@Model(adaptables = SlingHttpServletRequest.class,
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class CustomConfigurationModelImpl {
@OSGiService
private CustomConfigurationMethods customConfigurationMethods;
public String getFirstName() {
return customConfigurationMethods.getFirstName();
}
public int getLastName() {
return customConfigurationMethods.getLastName();
}
}
@SlingObject
@SlingObject injects common Sling objects into the model.
Common objects include:
ResourceResourceResolverSlingHttpServletRequestSlingHttpServletResponseSlingScriptHelper
Example:
@SlingObject
private Resource resource;
@SlingObject
private ResourceResolver resourceResolver;
@SlingObject
private SlingScriptHelper scriptHelper;
@ScriptVariable
@ScriptVariable injects script context objects such as the current page or resource.
Example:
@Model(
adaptables = Resource.class,
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
public class CustomModel {
@ValueMapValue
private String titleField;
@ScriptVariable
private Page currentPage;
@ScriptVariable
private Resource resource;
@ScriptVariable
private ResourceResolver resourceResolver;
@ScriptVariable
private SlingScriptHelper sling;
public String getTitleField() {
return titleField;
}
public Page getCurrentPage() {
return currentPage;
}
public Resource getResource() {
return resource;
}
public ResourceResolver getResourceResolver() {
return resourceResolver;
}
public SlingScriptHelper getSling() {
return sling;
}
}
@ChildResource
@ChildResource injects a child resource relative to the current resource.
Example JCR structure:
/content/myProject/jcr:content
+ myComponent
- titleField: "Title"
+ detailsField
- subtitleField: "Sub TitleField"
- description: "Detailed description."
+ items
+ item1
- name: "Item 1"
+ item2
- name: "Item 2"
Example model:
@Model(
adaptables = Resource.class,
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
public class CustomModel {
@ValueMapValue
private String titleField;
@ChildResource(name = "detailsField")
private Resource detailsField;
@ChildResource(name = "items")
private List<Resource> items;
public String getTitleField() {
return titleField;
}
public String getSubtitleField() {
return detailsField != null ? detailsField.getValueMap().get("subtitleField", String.class) : null;
}
public String getDescription() {
return detailsField != null ? detailsField.getValueMap().get("description", String.class) : null;
}
public List<Resource> getItems() {
return items;
}
public String getItemName(int index) {
if (items != null && items.size() > index) {
return items.get(index).getValueMap().get("name", String.class);
}
return null;
}
}
@ResourcePath
@ResourcePath injects a resource from a specified repository path.
Example JCR structure:
/content/myProject
+ jcr:content
- titleField: "Main Content"
+ referencedContent
- titleField: "Referenced Content"
Example model:
@Model(
adaptables = Resource.class,
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
public class CustomModel {
@ValueMapValue
private String titleField;
@ResourcePath(path = "/content/myProject/referencedContent")
private Resource referencedResource;
public String getTitleField() {
return titleField;
}
public String getReferencedTitle() {
if (referencedResource != null) {
ValueMap valueMap = referencedResource.getValueMap();
return valueMap.get("titleField", String.class);
}
return null;
}
}
@RequestAttribute
@RequestAttribute injects a request attribute from the current request.
Example controller code:
request.setAttribute("myAttribute", "This is a request attribute");
Example model:
@Model(
adaptables = SlingHttpServletRequest.class,
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
public class CustomModel {
@RequestAttribute(name = "myAttribute")
private String myAttribute;
public String getMyAttribute() {
return myAttribute;
}
}
@Designate
@Designate is used to bind an OSGi configuration to a service or model.
@Activate
@Activate marks a method that runs when an OSGi component is activated.
Best Practices for Writing Sling Models in AEM
,[object Object],
Why this matters
- Using both adaptables can create ambiguity about the model’s context.
Resourceis best for content-focused models.SlingHttpServletRequestis best for request-specific logic, parameters, and current page data.
Separation of concerns
- Use
Resourcewhen you need to work with JCR content and child nodes. - Use
SlingHttpServletRequestwhen you need request attributes, parameters, or WCM-specific objects.
Example: resource-focused model
@Model(adaptables = Resource.class)
public class Button {
@ValueMapValue
private String title;
}
Example: request-focused model
@Model(adaptables = SlingHttpServletRequest.class)
public class Button {
@Inject
private Page currentPage;
@Inject
private String parameter;
}
Handling both scenarios
If you need both resource and request data, adapt from SlingHttpServletRequest and inject the resource from that request. This keeps the model context clear.
@Model(adaptables = SlingHttpServletRequest.class)
public class Button {
@Inject
private Resource resource;
@Inject
private String requestParam;
public String getTitle() {
return resource.getValueMap().get("title", String.class);
}
public String getRequestParam() {
return requestParam;
}
}
Prefer specific annotations over @Inject
- Use
@ValueMapValuefor resource properties. - Use
@ChildResourcefor child resources. - Use
@OSGiServicefor OSGi services. - Use
@ScriptVariablefor script context objects.
Summary
Sling Models are a powerful way to connect AEM repository content to HTL views. Use the right adaptables and annotations to keep your models clean, maintainable, and easy to test.
If you want, I can also add a second article showing a real-time Sling Model component example with a complete HTL and Java implementation.