N
Naveenr.dev
Chapter 05
14 min read2026-06-17

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 Resource or SlingHttpServletRequest.
  • They keep content retrieval and business logic separate from the HTL template.

Features of Sling Models

  • Annotations-Driven: Uses annotations like @Model, @Inject, and @ValueMapValue to 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 Resource and SlingHttpServletRequest.
  • 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:

  1. A page author adds a component to a page.
  2. AEM resolves the component’s sling:resourceType to a path under /apps.
  3. AEM loads the HTL script for that component.
  4. The HTL references a Sling Model class using data-sly-use.
  5. AEM instantiates the Sling Model and injects required values.
  6. 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.class
  • SlingHttpServletRequest.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.class is used when the model is based on repository content.
  • SlingHttpServletRequest.class is 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.OPTIONAL sets missing values to null.
  • DefaultInjectionStrategy.REQUIRED throws 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 null if 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:

  • Resource
  • ResourceResolver
  • SlingHttpServletRequest
  • SlingHttpServletResponse
  • SlingScriptHelper

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.
  • Resource is best for content-focused models.
  • SlingHttpServletRequest is best for request-specific logic, parameters, and current page data.

Separation of concerns

  • Use Resource when you need to work with JCR content and child nodes.
  • Use SlingHttpServletRequest when 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 @ValueMapValue for resource properties.
  • Use @ChildResource for child resources.
  • Use @OSGiService for OSGi services.
  • Use @ScriptVariable for 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.