Skip to main content

Sling Model Annotations Reference

This page is a comprehensive reference for all commonly used Sling Model annotations. Each annotation is explained with its purpose, attributes, and practical examples. For deep dives on specific patterns, see the dedicated pages for @ChildResource and @ExternalizedValueMapValue.

Quick Reference

AnnotationSource PackagePurpose
@Modelo.a.s.models.annotationsDeclares a class as a Sling Model
@ValueMapValueo.a.s.models.annotations.injectorspecificInjects a JCR property from the resource's ValueMap
@ChildResourceo.a.s.models.annotations.injectorspecificInjects child resources (nodes) as models or resources
@Selfo.a.s.models.annotations.injectorspecificInjects the adaptable itself or delegates to another model
@SlingObjecto.a.s.models.annotations.injectorspecificInjects core Sling objects (Resource, Request, ResourceResolver)
@OSGiServiceo.a.s.models.annotations.injectorspecificInjects OSGi services
@ScriptVariableo.a.s.models.annotations.injectorspecificInjects HTL scripting bindings (currentPage, etc.)
@RequestAttributeo.a.s.models.annotations.injectorspecificReads request attributes (e.g. from data-sly-use parameters)
@ResourcePatho.a.s.models.annotations.injectorspecificInjects a Resource resolved by a JCR path
@Injectjavax.injectGeneric injection (legacy -- prefer specific annotations)
@Namedjavax.injectOverrides the property name for @Inject
@Sourceo.a.s.models.annotationsSelects a specific injector for @Inject
@Viao.a.s.models.annotationsChanges the adaptable source for injection
@Filtero.a.s.models.annotationsFilters OSGi service references by LDAP expression
@Defaulto.a.s.models.annotationsProvides fallback values when injection returns null
@PostConstructjavax.annotationRuns initialization logic after all fields are injected
@Exportero.a.s.models.annotationsEnables JSON/XML export of the model
tip

The injector-specific annotations (like @ValueMapValue, @OSGiService, @SlingObject) are always preferred over the generic @Inject. They are self-documenting, type-safe, and avoid ambiguity about which injector is used.


@Model

Declares a Java class as a Sling Model. This is required on every Sling Model class.

@Model(
adaptables = SlingHttpServletRequest.class,
adapters = {MyModel.class, ComponentExporter.class},
resourceType = "myproject/components/mycomponent",
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
public class MyModel implements ComponentExporter { ... }
AttributeTypePurpose
adaptablesClass<?>[]What the model can be adapted from (SlingHttpServletRequest.class, Resource.class, or both)
adaptersClass<?>[]What interfaces/classes the model can be adapted to (used for adaptTo() lookups)
resourceTypeStringBinds the model to a specific sling:resourceType (enables automatic resolution)
defaultInjectionStrategyDefaultInjectionStrategyOPTIONAL or REQUIRED -- controls what happens when fields can't be injected

adaptables: Request vs Resource

The choice of adaptable affects which injectors work and what data is available:

AdaptableAvailable injectionsWhen to use
SlingHttpServletRequest.classEverything -- request, resource, session, WCM mode, script variablesDefault choice for components rendered via HTL
Resource.classResource properties, child resources, OSGi servicesUtility models, child models for @ChildResource, unit tests
info

If you use Request as the adaptable, you can still access the underlying resource via @SlingObject. If you use Resource, you cannot access request-scoped data (WCM mode, selectors, request attributes).

defaultInjectionStrategy

// OPTIONAL: missing fields get null instead of failing the entire model
@Model(adaptables = Resource.class,
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class SafeModel {
@ValueMapValue
private String title; // null if property doesn't exist -- model still works
}

// REQUIRED (default): any missing field causes adaptTo() to return null
@Model(adaptables = Resource.class)
public class StrictModel {
@ValueMapValue
private String title; // if missing, adaptTo(StrictModel.class) returns null entirely
}
tip

Always set defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL and only mark specific fields as REQUIRED when truly necessary. This prevents the entire model from silently returning null when one optional field is missing.


@ValueMapValue

Injects a property from the resource's ValueMap (the JCR node properties). This is the most commonly used annotation in AEM Sling Models.

@ValueMapValue
private String title;

@ValueMapValue
private String[] tags;

@ValueMapValue
private boolean hideInNav;

@ValueMapValue
private Long maxItems;

@ValueMapValue
private Calendar publishDate;
AttributePurpose
nameOverride the JCR property name (defaults to the Java field name)
injectionStrategyOPTIONAL, REQUIRED, or DEFAULT
viaChange the resource to read from (e.g. "resource")

Property name mapping

// Field name matches JCR property name
@ValueMapValue
private String jcr:title; // Won't compile -- invalid Java identifier!

// Use 'name' to map to JCR properties with special characters
@ValueMapValue(name = "jcr:title")
private String pageTitle;

// Or when the field name simply differs from the property name
@ValueMapValue(name = "cq:lastModified")
private Calendar lastModified;

Type coercion

Sling automatically coerces property values to the Java field type where possible:

JCR TypeJava Field TypeNotes
StringStringDirect
String[]String[], List<String>Multi-value properties
LongLong, Integer, int, longNumeric coercion
BooleanBoolean, booleanDirect
DateCalendar, DateJCR dates are Calendar internally
DecimalBigDecimalDirect

@ChildResource

Injects child JCR nodes as Sling Models or Resources. Essential for composite multifield dialogs.

// Inject all children of the "links" node as a list
@ChildResource
private List<LinkItem> links;

// Named child node
@ChildResource(name = "configuration")
private AppConfig config;

// As raw resources
@ChildResource
private List<Resource> items;

See the dedicated @ChildResource deep dive for complete documentation including dialog configuration, JCR structure, and patterns.


@Self

Injects the adaptable itself or delegates to another Sling Model adapted from the same adaptable. This is one of the most powerful -- and most misunderstood -- annotations.

Inject the adaptable directly

@Model(adaptables = SlingHttpServletRequest.class)
public class MyModel {

// Injects the SlingHttpServletRequest itself
@Self
private SlingHttpServletRequest request;
}

Delegate to another Sling Model

The more common use case: adapt the same request/resource to a different model within your model.

@Model(adaptables = SlingHttpServletRequest.class,
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ProductPageModel {

// Adapts the same request to the HeroModel
@Self
private HeroModel hero;

// Adapts the same request to the BreadcrumbModel
@Self
private BreadcrumbModel breadcrumb;

public HeroModel getHero() { return hero; }
public BreadcrumbModel getBreadcrumb() { return breadcrumb; }
}

This is useful for composition -- building a page-level model from multiple component models without duplicating logic.

@Self with @Via

When the model adapts from a request but you need to delegate via the resource:

@Model(adaptables = SlingHttpServletRequest.class)
public class WrapperModel {

// Adapts the underlying resource (not the request) to ChildModel
@Self @Via(type = ResourceSuperType.class)
private ChildModel delegateModel;
}
AttributePurpose
injectionStrategyOPTIONAL, REQUIRED, or DEFAULT
warning

@Self delegation creates a new model instance via adaptTo(). If the target model's adaptTo() returns null (e.g. due to a missing required field), the @Self field will also be null. Always use OPTIONAL injection or null-check.


@SlingObject

Injects core Sling framework objects. What you can inject depends on the adaptable:

@Model(adaptables = SlingHttpServletRequest.class,
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class MyModel {

// The current resource (same as request.getResource())
@SlingObject
private Resource resource;

// The resource resolver
@SlingObject
private ResourceResolver resourceResolver;

// The request itself
@SlingObject
private SlingHttpServletRequest request;

// The response
@SlingObject
private SlingHttpServletResponse response;
}
InjectableFrom RequestFrom Resource
ResourceYesYes (the adaptable itself)
ResourceResolverYesYes
SlingHttpServletRequestYesNo
SlingHttpServletResponseYesNo
tip

Use @SlingObject for Sling framework objects. Don't use @Inject for these -- @SlingObject is explicit and avoids ambiguity.


@OSGiService

Injects an OSGi service registered in the Felix container. This is how your Sling Models access shared services, configurations, and APIs.

@Model(adaptables = SlingHttpServletRequest.class,
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class SearchModel {

@OSGiService
private QueryBuilder queryBuilder;

@OSGiService
private XSSAPI xssApi;

@OSGiService
private ModelFactory modelFactory;

@OSGiService
private Externalizer externalizer;
}

Filtering services

When multiple implementations of the same interface exist, use the filter attribute with an LDAP expression:

// Inject only the service where component.name matches
@OSGiService(filter = "(component.name=com.myproject.impl.SpecialSearchService)")
private SearchService searchService;

// Filter by a custom service property
@OSGiService(filter = "(service.type=premium)")
private NotificationService notificationService;
AttributePurpose
filterLDAP filter expression to select a specific service implementation
injectionStrategyOPTIONAL, REQUIRED, or DEFAULT

Injecting multiple services

// Inject all implementations as a list
@OSGiService
private List<EventHandler> eventHandlers;

@ScriptVariable

Injects variables from the HTL scripting bindings. These are context objects that AEM/Sling makes available during page rendering.

@Model(adaptables = SlingHttpServletRequest.class,
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class NavigationModel {

@ScriptVariable
private Page currentPage;

@ScriptVariable
private Designer currentDesign;

@ScriptVariable
private Style currentStyle;

@ScriptVariable
private PageManager pageManager;

@ScriptVariable
private ComponentContext componentContext;

@ScriptVariable
private ValueMap properties;

public String getPageTitle() {
return currentPage.getTitle();
}

public int getDepth() {
return currentPage.getDepth();
}
}

Available script variables

VariableTypeDescription
currentPagecom.day.cq.wcm.api.PageThe current page being rendered
resourcePagecom.day.cq.wcm.api.PageThe page that contains the resource (may differ from currentPage for included components)
pageManagercom.day.cq.wcm.api.PageManagerAPI for page CRUD operations
currentDesigncom.day.cq.wcm.api.designer.DesignThe current design
currentStylecom.day.cq.wcm.api.designer.StyleThe current style (design cell)
componentContextcom.day.cq.wcm.api.components.ComponentContextComponent rendering context
propertiesorg.apache.sling.api.resource.ValueMapProperties of the current resource
pagePropertiesorg.apache.sling.api.resource.ValueMapProperties of the current page's jcr:content node
componentcom.day.cq.wcm.api.components.ComponentThe current component definition
resourceorg.apache.sling.api.resource.ResourceThe current resource
resolverorg.apache.sling.api.resource.ResourceResolverThe resource resolver
warning

@ScriptVariable only works when the model is adapted from SlingHttpServletRequest. It requires the HTL scripting context to be active, which means it won't work in unit tests without mocking the bindings. Consider using @SlingObject for resource/resolver and injecting PageManager via resourceResolver.adaptTo(PageManager.class) in a @PostConstruct if you need better testability.


@RequestAttribute

Reads attributes from the SlingHttpServletRequest. This is the standard way to pass parameters from HTL data-sly-use calls into your Sling Model.

Passing parameters from HTL

HTL template
<sly data-sly-use.card="${'com.myproject.core.models.CardModel' @ title='Featured', maxItems=5}"/>
<h2>${card.title}</h2>
CardModel.java
@Model(adaptables = SlingHttpServletRequest.class,
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class CardModel {

// Reads the "title" request attribute set by data-sly-use
@RequestAttribute
private String title;

// Reads the "maxItems" request attribute
@RequestAttribute
private int maxItems;

public String getTitle() { return title; }
public int getMaxItems() { return maxItems > 0 ? maxItems : 10; }
}
AttributePurpose
nameOverride the attribute name (defaults to the Java field name)
injectionStrategyOPTIONAL, REQUIRED, or DEFAULT

Passing request attributes programmatically

Request attributes can also be set by servlets, filters, or other models:

// In a filter or servlet
request.setAttribute("analyticsId", "abc-123");

// In a model
@RequestAttribute(name = "analyticsId")
private String trackingId;
info

Request attributes set via data-sly-use are only available for that specific model invocation. They are not available to other models on the same page unless explicitly forwarded.


@ResourcePath

Injects a Resource by resolving a JCR path. The path can come from a JCR property or be hardcoded.

// Resolve a path stored in a JCR property
@ResourcePath(name = "targetPage")
private Resource targetPageResource;

// Hardcoded path
@ResourcePath(path = "/content/dam/myproject/fallback-image")
private Resource fallbackImage;

// Inject multiple paths
@ResourcePath(name = "relatedPages")
private List<Resource> relatedPages;
AttributePurpose
pathHardcoded JCR path to resolve
nameJCR property name that contains the path (reads the value, then resolves it)
injectionStrategyOPTIONAL, REQUIRED, or DEFAULT

Practical example

@Model(adaptables = SlingHttpServletRequest.class,
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class RelatedContentModel {

// Author picks a page path in the dialog (stored as "./targetPage" property)
@ResourcePath(name = "targetPage")
private Resource targetPageResource;

public String getTargetTitle() {
if (targetPageResource == null) return null;
Page page = targetPageResource.adaptTo(Page.class);
return page != null ? page.getTitle() : null;
}
}

@Inject

The generic injection annotation from javax.inject. Sling Models tries all registered injectors in priority order until one succeeds.

@Inject
private String title; // Could come from ValueMap, request attribute, or other injector
warning

@Inject is ambiguous -- you can't tell which injector will handle it by reading the code. Always prefer the specific annotations (@ValueMapValue, @OSGiService, @SlingObject, etc.) instead. They are self-documenting and avoid unexpected injection sources.

When @Inject is still useful

The main remaining use case is injecting child resources with the @Source annotation:

@Inject @Source("child-resources")
private List<Resource> items;

This is equivalent to @ChildResource but uses the generic mechanism.

Injector priority order

When using @Inject, Sling tries injectors in this order:

PriorityInjectorWhat it provides
HighestScript BindingsHTL scripting variables
HighValueMapJCR node properties
MediumChild ResourcesChild nodes
MediumRequest AttributesRequest attributes
LowOSGi ServicesOSGi service references
LowestSling ObjectSling framework objects

@Named

Overrides the property/parameter name for @Inject. Only works with @Inject, not with the injector-specific annotations (which have their own name attribute).

// Without @Named -- field name "title" is used as the JCR property name
@Inject
private String title;

// With @Named -- reads the "jcr:title" property instead
@Inject @Named("jcr:title")
private String pageTitle;
tip

Since @ValueMapValue has a built-in name attribute, prefer @ValueMapValue(name = "jcr:title") over @Inject @Named("jcr:title").


@Source

Selects a specific injector when using @Inject. This removes the ambiguity of which injector handles the injection.

// Explicitly use the child-resources injector
@Inject @Source("child-resources")
private List<Resource> items;

// Explicitly use the osgi-services injector
@Inject @Source("osgi-services")
private QueryBuilder queryBuilder;
Source valueInjector
"script-bindings"Script variables (currentPage, etc.)
"valuemap"JCR properties
"child-resources"Child nodes
"request-attributes"Request attributes
"osgi-services"OSGi services
"sling-object"Sling framework objects
tip

If you find yourself using @Inject @Source(...), you should almost certainly switch to the equivalent injector-specific annotation instead. For example, @Inject @Source("valuemap") is just a verbose way of writing @ValueMapValue.


@Via

Changes where the injected value comes from. By default, injection reads from the adaptable (the request or resource). @Via redirects injection to a different source.

Common use: read from the resource when adapting from request

@Model(adaptables = SlingHttpServletRequest.class)
public class MyModel {

// Without @Via: reads from the request's ValueMap (usually empty)
// With @Via: reads from the underlying resource's ValueMap
@Inject @Via("resource")
private String title;
}
info

This specific pattern is rarely needed because @ValueMapValue already reads from the resource's ValueMap automatically, even when the model adapts from a request. @Via("resource") is mainly useful with @Inject.

Via types for delegation

// Delegate to the resource's child node
@Self @Via(value = "jcr:content", type = ChildResource.class)
private ContentModel contentModel;

// Delegate to the resource super type
@Self @Via(type = ResourceSuperType.class)
private ParentModel parentModel;

// Via a specific child path using BeanProperty
@ValueMapValue @Via(value = "jcr:content", type = ChildResource.class)
private String pageTitle;
Via typePurpose
ChildResource.classAdapts from a child resource at the given path
ResourceSuperType.classDelegates to the resource super type's model
BeanProperty.classReads from a bean property of the adaptable
ForcedResourceType.classForces a different resource type for adaptation

@Filter

Applies an LDAP filter when injecting OSGi services via @Inject. This is the @Inject equivalent of @OSGiService(filter = ...).

@Inject @Source("osgi-services")
@Filter("(component.name=com.myproject.impl.PremiumSearch)")
private SearchService searchService;
tip

Prefer @OSGiService(filter = "...") which combines source selection and filtering in a single annotation.


@Default

Provides a fallback value when injection returns null. Works with @Inject and with the injector-specific annotations.

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

@ValueMapValue
@Default(intValues = 10)
private int maxItems;

@ValueMapValue
@Default(booleanValues = false)
private boolean hideInNav;

@ValueMapValue
@Default(longValues = 0L)
private long sortOrder;
AttributeJava Type
valuesString[]
intValuesint[]
longValueslong[]
doubleValuesdouble[]
floatValuesfloat[]
booleanValuesboolean[]
shortValuesshort[]
tip

For simple defaults, a null-check in your getter is often cleaner and more readable:

@ValueMapValue
private String title;

public String getTitle() {
return StringUtils.defaultIfBlank(title, "Untitled");
}

Use @Default when you want the default applied at injection time (before @PostConstruct runs).


@PostConstruct

A javax.annotation lifecycle annotation. The annotated method runs after all fields have been injected, giving you a place to perform computed initialization.

@Model(adaptables = SlingHttpServletRequest.class,
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ArticleModel {

@ValueMapValue
private String title;

@ValueMapValue
private Calendar publishDate;

@OSGiService
private DateFormatterService dateFormatter;

private String formattedDate;
private String slug;

@PostConstruct
protected void init() {
// Compute derived values after injection
if (publishDate != null) {
formattedDate = dateFormatter.format(publishDate);
}
if (title != null) {
slug = title.toLowerCase().replaceAll("[^a-z0-9]+", "-");
}
}

public String getFormattedDate() { return formattedDate; }
public String getSlug() { return slug; }
}

Rules

  • The method must be void, take no arguments, and be protected (convention, not enforced)
  • Only one @PostConstruct method per class -- if the superclass has one, both run (super first)
  • If the method throws an exception, adaptTo() returns null
  • Keep it lightweight -- avoid expensive operations (see Performance)

@Exporter

Enables the Sling Model Exporter framework, which allows you to serialize the model as JSON (or other formats) via a .model.json URL.

@Model(
adaptables = SlingHttpServletRequest.class,
adapters = {MyModel.class, ComponentExporter.class},
resourceType = "myproject/components/mycomponent"
)
@Exporter(name = "jackson", extensions = "json")
public class MyModel implements ComponentExporter {

@ValueMapValue
private String title;

public String getTitle() { return title; }

@Override
public String getExportedType() {
return "myproject/components/mycomponent";
}
}
AttributePurpose
nameExporter name ("jackson" for JSON)
extensionsURL extension that triggers the export ("json")
selectorURL selector (defaults to "model")

Requesting /content/my-site/en/home/jcr:content/mycomponent.model.json returns:

{
":type": "myproject/components/mycomponent",
"title": "Hello World"
}
info

To participate in the JSON export, your model must implement ComponentExporter and be registered with the adapters attribute. This is required for AEM SPA Editor integration.


Annotation Combinations Cheat Sheet

Common annotation combinations you'll see in real AEM projects:

@Model(adaptables = SlingHttpServletRequest.class,
adapters = {MyComponent.class, ComponentExporter.class},
resourceType = MyComponent.RESOURCE_TYPE,
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
@Exporter(name = "jackson", extensions = "json")
public class MyComponent implements ComponentExporter {

static final String RESOURCE_TYPE = "myproject/components/mycomponent";

// --- JCR Properties ---
@ValueMapValue
private String title;

@ValueMapValue(name = "jcr:description")
private String description;

@ValueMapValue
@Default(values = "default")
private String variant;

// --- Child Resources (multifield) ---
@ChildResource
private List<LinkItem> links;

// --- Page Context ---
@ScriptVariable
private Page currentPage;

// --- Sling Objects ---
@SlingObject
private Resource resource;

@SlingObject
private ResourceResolver resourceResolver;

// --- OSGi Services ---
@OSGiService
private XSSAPI xssApi;

// --- Delegation ---
@Self
private SlingHttpServletRequest request;

// --- HTL Parameters ---
@RequestAttribute
private String cssClass;

// --- Computed fields ---
private String safeTitle;
private String pagePath;

@PostConstruct
protected void init() {
safeTitle = xssApi.encodeForHTML(title);
pagePath = currentPage != null ? currentPage.getPath() : "";
}

// Getters...

@Override
public String getExportedType() { return RESOURCE_TYPE; }
}

See also