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/backend/sling-model-and-services.md.
Sling Models are annotation-driven Java classes that automatically map AEM content (JCR properties, child resources, OSGi services) to Java objects. They are the standard approach for implementing component logic in AEM and replace the older WCMUsePojo and JavaScript Use-API patterns.
This page covers how to build, use, and test Sling Models, as well as the common AEM service APIs you'll use alongside them.
Use @Via("resource") when adapting from request to access resource properties
[!-success] -success
tip
Always prefer SlingHttpServletRequest.class for component models. Use Resource.class only for child models (e.g. multifield items via @ChildResource) or utility models that don't need request context.
When you specify resourceType in the @Model annotation, Sling automatically selects this model when HTL uses data-sly-use without a fully qualified class name:
<!-- Sling resolves the model automatically via the component's sling:resourceType --><sly data-sly-use.model="com.myproject.core.models.TeaserModel"/><!-- Or even shorter if resourceType is set: --><sly data-sly-use.model="myproject/components/teaser"/>
For a complete reference of all Sling Model annotations (@ValueMapValue, @OSGiService, @Self, @ChildResource, @ScriptVariable, @RequestAttribute, @Via, @Default, etc.), see the dedicated Sling Model Annotations Reference.
The Sling Model Exporter framework lets you serialize a model as JSON via a .model.json URL. This is essential for headless AEM, SPA Editor, and Content as a Service scenarios.
Editable template policies let template authors configure component settings (e.g. allowed heading levels, default variants) via the template editor. These settings are stored as content policies and can be read in your Sling Model.
The ResourceResolver is the central API for accessing JCR content in Sling. Understanding its lifecycle is critical to avoiding resource leaks and session issues.
Never close an implicit ResourceResolver. Closing the request's resolver (request.getResourceResolver()) kills the underlying JCR session for the entire request, breaking all subsequent processing.
Always close explicit ResourceResolvers. Service resolvers opened via ResourceResolverFactory must be closed, ideally using try-with-resources.
Rule of thumb: close the resolver where you opened it. If a resolver is passed as a parameter, the caller is responsible for its lifecycle.
Business logic that is shared across components, servlets, or workflows should live in OSGi services. Sling Models should stay focused on component rendering; complex logic belongs in services.
A TransportHandler intercepts the replication pipeline and lets you route content to external systems (CI/CD pipelines, CDNs, search indexes, etc.) during activation:
@Component( service = TransportHandler.class, immediate = true, property = { "label=External CDN Purge Handler" })public class ExternalCDNTransportHandler implements TransportHandler { private static final Logger LOG = LoggerFactory.getLogger( ExternalCDNTransportHandler.class); /** * Return true if this handler should process the replication event. * Match by agent configuration (e.g. a custom transport URI scheme). */ @Override public boolean canHandle(AgentConfig agentConfig) { String transportURI = agentConfig.getTransportURI(); return transportURI != null && transportURI.startsWith("cdn://"); } /** * Called when content is replicated through a matching agent. * Use this to push content or purge caches on external systems. */ @Override public ReplicationResult deliver(TransportContext ctx, ReplicationTransaction tx) throws ReplicationException { String path = tx.getAction().getPath(); LOG.info("Purging CDN cache for: {}", path); try { // Call external CDN API to purge the path purgeExternalCDN(path); return ReplicationResult.OK; } catch (Exception e) { LOG.error("CDN purge failed for {}", path, e); return new ReplicationResult(false, 0, "CDN purge failed: " + e.getMessage()); } } private void purgeExternalCDN(String path) { // HTTP call to CDN invalidation API }}
The JavaScript Use-API (server-side Rhino, ES5 only) is legacy. Always prefer Sling Models for new components. It is slower, harder to test, and has limited API access. This section is included for reference when working with legacy codebases.
mycomponent/├── mycomponent.html└── helper.js
use(function () { 'use strict'; // 'this' contains variables passed from HTL via data-sly-use var inputValue = this.someVar; // ⚠️ Runs server-side (Rhino engine) - no console.log, no window, no DOM // ⚠️ Must use ES5 syntax - arrow functions, let/const, template literals will fail return { processedValue: inputValue ? inputValue.toUpperCase() : 'DEFAULT' };});
@BeforeEachvoid setUp() { context.addModelsForClasses(ArticleModel.class); // Register a mock or real service context.registerService(DateFormatterService.class, new DateFormatterServiceImpl());}
Sling Models should focus on data access and simple transformations. Complex business logic belongs in OSGi services that the model injects via @OSGiService.
Set defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL on every model. This prevents the entire model from returning null when a single optional field is missing. Mark truly required fields with injectionStrategy = InjectionStrategy.REQUIRED.
Sling Models are request-scoped - a new instance is created for each request. Never store cross-request state in a model. Use OSGi services with thread-safe patterns for shared state.