Sling Models

Understand Sling Models — the backbone of AEM component logic. Learn how they work, how to write them, and the gotchas that trip up every developer.

Last reviewed: December 2024

·AEM 6.2+AEM 6.5AEM as a Cloud Service
✅
Quick Understanding: A Sling Model is a Java class that reads data from a JCR node (or HTTP request) and makes it available to your HTL template. Think of it as the "ViewModel" in MVC — it bridges the raw JCR data and your HTML rendering.

🔍 What Is It?

A Sling Model is a Java POJO (Plain Old Java Object) annotated with @Model. It:

  • →Adapts a Resource or SlingHttpServletRequest to a typed Java object
  • →Automatically injects JCR property values using @ValueMapValue
  • →Can inject OSGi services using @OSGiService
  • →Can inject child resources, parent pages, and request parameters
  • →Is used by HTL templates via data-sly-use

Sling Models replaced the older WCMUsePojo and are now the standard way to write AEM component logic.

🎯 Why Do We Need It?

HTL is intentionally limited — it can't do complex logic, call services, or format data. That's by design. Sling Models fill that gap:

  • →Separation of concerns — HTL handles rendering, Sling Models handle logic
  • →Testability — Java classes are easy to unit test
  • →Type safety — You get compile-time checking instead of runtime errors
  • →Clean HTL — Your templates stay readable and maintainable
  • →Service access — You can call OSGi services, APIs, and databases from a model

Without Sling Models, you'd either put logic in HTL (bad) or use scriptlets (very bad).

🏗️ What Happens in Real Projects?

Here's how Sling Models are used in a real project:

Scenario 1: Reading component properties

An author fills in a dialog with a heading, description, and link. Your Sling Model reads those JCR properties and exposes them as typed Java getters to HTL.

Scenario 2: Calling an external API

A "Latest News" component needs to fetch articles from a CMS API. The Sling Model injects a NewsService (OSGi service) and returns a list of NewsItem objects to HTL.

Scenario 3: Page-level data

A breadcrumb component needs the current page's ancestors. The Sling Model uses @ScriptVariable to get the currentPage and traverses the page tree.

Scenario 4: Exporter for headless

A Sling Model with @Exporter annotation exposes component data as JSON for headless delivery via Content Services.

⚙️ How It Works

Injection Strategy

When AEM instantiates your Sling Model, it goes through each field annotated with @Inject, @ValueMapValue, @ChildResource, etc., and tries to resolve the value from the adaptable (Resource or Request).

Adaptables

  • →Resource.class — Use when you only need JCR properties. Faster, simpler.
  • →SlingHttpServletRequest.class — Use when you need request parameters, selectors, or suffixes. Can also access the resource through the request.

Key Annotations

AnnotationWhat it does
@ModelMarks the class as a Sling Model
@ValueMapValueInjects a JCR property by field name
@InjectGeneric injection (tries multiple strategies)
@OSGiServiceInjects an OSGi service
@ScriptVariableInjects HTL script variables (currentPage, etc.)
@ChildResourceInjects a child node as a Resource
@RequestAttributeInjects a request attribute
@PostConstructRuns after all injections complete
@SelfInjects the adaptable itself

DefaultInjectionStrategy

Always use DefaultInjectionStrategy.OPTIONAL unless a field is truly required. With REQUIRED (the default), if any injection fails, the entire model fails to instantiate — causing a blank component.

💻 Example

A Complete Sling Model Example — A News Card component that reads JCR properties, calls an OSGi service, and exposes data to HTL.

java
package com.myproject.core.models;

import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.DefaultInjectionStrategy;
import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;
import org.apache.sling.models.annotations.injectorspecific.OSGiService;
import org.apache.sling.models.annotations.injectorspecific.ScriptVariable;
import com.day.cq.wcm.api.Page;
import javax.annotation.PostConstruct;
import java.util.List;

@Model(
    adaptables = SlingHttpServletRequest.class,
    adapters = NewsCardModel.class,
    defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL,
    resourceType = "myproject/components/newscard"
)
public class NewsCardModel {

    // Injects the JCR property "heading" from the component's node
    @ValueMapValue
    private String heading;

    @ValueMapValue
    private String description;

    @ValueMapValue
    private String linkPath;

    // Injects an OSGi service
    @OSGiService
    private NewsService newsService;

    // Injects the current page from HTL context
    @ScriptVariable
    private Page currentPage;

    private List<NewsItem> latestNews;

    // Runs after all injections are complete
    @PostConstruct
    protected void init() {
        if (newsService != null) {
            latestNews = newsService.getLatestNews(3);
        }
    }

    public String getHeading() {
        return heading != null ? heading : "Latest News";
    }

    public String getDescription() { return description; }

    public String getLinkPath() { return linkPath; }

    public List<NewsItem> getLatestNews() { return latestNews; }

    public String getCurrentPageTitle() {
        return currentPage != null ? currentPage.getTitle() : "";
    }
}

🤔 Common Confusions

❓ When should I use Resource vs SlingHttpServletRequest as adaptable?

Use Resource when you only need JCR properties — it's simpler and faster. Use SlingHttpServletRequest when you need request context: selectors, suffixes, query parameters, or request attributes. If you're using @ScriptVariable or @RequestAttribute, you need the Request.

❓ Why does my Sling Model return null even though the property exists in CRXDE?

Most common causes: 1) The field name doesn't match the JCR property name — use @ValueMapValue(name="propertyName") to specify it explicitly. 2) You used DefaultInjectionStrategy.REQUIRED and something else failed, causing the whole model to fail silently. 3) The property is on a different node than you're adapting.

❓ What is the difference between @Inject and @ValueMapValue?

@Inject is generic — it tries multiple injection strategies in order. @ValueMapValue specifically reads from the ValueMap (JCR properties). Always prefer @ValueMapValue for JCR properties — it's explicit and faster.

❓ Can I use Sling Models for page-level logic, not just component logic?

Yes. You can adapt any Resource, including page nodes. Many projects create a "PageModel" that provides common page-level data (navigation, metadata, breadcrumbs) that all page templates use.

🚨 Common Production Issues

Model returns null

OSGi bundle not deployed, or bundle is in "Installed" state instead of "Active". Check Felix Console (/system/console/bundles).

PostConstruct throws NullPointerException

an injected OSGi service is null because the service isn't registered. Always null-check injected services.

Model works in Author but fails in Publish

an OSGi service that exists on Author isn't configured on Publish (e.g., a service that calls an internal Author API).

Sling Model not found

the resourceType in @Model doesn't match the component's sling:resourceType, or the package isn't in the bundle's scan path.

Circular injection

Model A injects Model B which injects Model A. AEM will throw a StackOverflowError.

Slow page rendering

a Sling Model calls an external API, QueryBuilder, or deep JCR traversal during every request. Move expensive work to a service with caching or precomputed content.

Intermittent data leakage

request-specific values are stored in static fields or singleton OSGi services instead of request-scoped model state.

Exporter JSON changes break a front-end app

a getter was renamed or removed without versioning the contract.

✅ Best Practices

Always use DefaultInjectionStrategy.OPTIONAL

it prevents the entire model from failing when one optional field is missing.

Use @ValueMapValue(name="propertyName") when the Java field name doesn't match the JCR property name.

Keep @PostConstruct lightweight

avoid heavy computations or API calls that could slow down every page render.

Write unit tests for every model using wcm.io AemContext

it saves hours of debugging.

Use the resourceType attribute in @Model to bind the model to a specific component, enabling automatic adaptation.

Prefer @OSGiService over manual service lookup

it's cleaner and testable.

Return empty collections (Collections.emptyList()) instead of null from list getters.

Keep external calls out of @PostConstruct unless they are fast, cached, timeout-protected, and truly required for rendering.

Treat Sling Model Exporter output as an API contract; add tests around JSON shape for headless or SPA consumers.

Use constructor or @PostConstruct validation to fail gracefully with author-friendly fallback output instead of blank components.

🏛️ Architect's Note

Senior Architect's Perspective

Sling Models are the single most important Java pattern in AEM development. Get this right and everything else becomes easier.

Architecture principle: Sling Models should be thin adapters, not service layers. If you find yourself writing complex business logic in a Sling Model, extract it into an OSGi service and inject it.

For AEM as a Cloud Service: Sling Models with @Exporter(name="jackson", extensions="json") automatically expose your component data as JSON endpoints — critical for headless and hybrid delivery patterns.

Versioning: When you change a Sling Model's interface significantly, consider creating a new version (e.g., HeroModelV2) rather than modifying the existing one — especially if other teams depend on it.

💬 Frequently Asked Questions

Can a Sling Model extend another Sling Model?

Yes, but be careful. Java inheritance works normally. The child model inherits all injections from the parent. However, @PostConstruct in the parent runs before the child's. A cleaner pattern is composition — inject the parent model rather than extending it.

How do I inject the current page in a Sling Model?

Use @ScriptVariable private Page currentPage; (requires SlingHttpServletRequest adaptable). Or use @Self SlingHttpServletRequest request and then request.getResource().adaptTo(Page.class). The @ScriptVariable approach is cleaner.

What is Sling Models Delegation Pattern?

It's a way to extend Core Components without copying their code. Your model implements the Core Component's interface and delegates unimplemented methods to the Core Component's model using @Self and @Via(type = ResourceSuperType.class).

Can I use Sling Models without HTL?

Yes. Sling Models can be used in Servlets, Filters, Workflows, and any Java code that has access to a Resource or Request. They're not tied to HTL.

How do I access multi-value properties?

Use @ValueMapValue private String[] tags; or List<String> tags. AEM's ValueMap handles the conversion automatically from JCR String[] to Java arrays and lists.

🎯 Key Takeaways

1Sling Models are the ViewModel — they bridge JCR data and HTL templates
2Always use DefaultInjectionStrategy.OPTIONAL to prevent silent failures
3Use @ValueMapValue for JCR properties, @OSGiService for services
4@PostConstruct runs after all injections — use it for initialization logic
5Test every model with AemContext — it's fast and catches real bugs
6Keep models thin — complex logic belongs in OSGi services

🎯 Practice this topic in interviews

↗ Where this topic fits

Sources

Sources / References

Use these AEM references to verify the concept in official docs and continue into the most relevant SplashRide pages.

Related SplashRide pages

SplashRide AEM learning path

Browse the full AEM topic collection and learning order on SplashRide.

AEM Interview Prep

Practice AEM interview questions and scenario-based answers.

AEM Developer Career Path

This AEM developer career path explains how to become an AEM developer through JCR, Sling, HTL, client libraries, Dispatcher, Cloud Service delivery, project depth, and interview-ready enterprise implementation skills.

Drupal Developer Career Path

A practical route through Drupal content modeling, site building, theming, modules, configuration management, APIs, performance, deployment discipline, and interview preparation.

AEM Components Explained: Dialogs, HTL, Sling Models, and Core Components

Learn AEM components with component nodes, dialogs, HTL, Sling Models, Core Components, authoring, inheritance, and real project patterns.

HTL (HTML Template Language)

Master HTL — AEM's templating language. Learn the syntax, best practices, and how it keeps your templates clean and secure.