All AEM as a Cloud Service skills: component development, Workflow model design, development, triggering, launchers, debugging, and triaging, Dispatcher config authoring, advisory, incident response, performance tuning, and security hardening, Rapid Development Environment (RDE) deployment, log inspection, snapshots, and troubleshooting via `aio aem rde`, Jackrabbit Oak query/index tuning (property, Lucene index definitions), and extracting/inventorying JCR queries (XPath, SQL2, QueryBuilder) across a codebase.
85%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Low
Low-risk findings worth noting
Patterns for creating AEM Sling Models following Adobe best practices.
core/src/main/java/[package-path]/models/{ComponentName}Model.javaNote: [package-path] = [package] with dots replaced by slashes (e.g., com/mysite/core)
// Include Apache 2.0 license header (see java-standards.md)
package [package].models;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.DefaultInjectionStrategy;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;
@Model(adaptables = Resource.class, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ComponentNameModel {
@ValueMapValue
private String title;
@ValueMapValue
private String description;
public String getTitle() {
return title;
}
public String getDescription() {
return description;
}
public boolean hasContent() {
return title != null || description != null;
}
}@ValueMapValue
private String title;
@ValueMapValue
private String description;
@ValueMapValue
private String linkText;
@ValueMapValue
private String linkURL;@ValueMapValue(name = "jcr:title")
private String pageTitle;@ValueMapValue
@Default(booleanValues = false)
private boolean enabled;
@ValueMapValue
@Default(intValues = 10)
private int maxItems;
@ValueMapValue
@Default(values = "default")
private String variant;// Single child resource (e.g., image)
@ChildResource(name = "image")
private Resource imageResource;
// List of child resources (for multifield)
@ChildResource(name = "items")
private List<Resource> itemResources;package [package].models;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.annotation.PostConstruct;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.DefaultInjectionStrategy;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.ChildResource;
import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;
@Model(adaptables = Resource.class, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class TilesListModel {
@ValueMapValue
private String listTitle;
@ChildResource(name = "tiles")
private List<Resource> tileResources;
private List<TileItem> tiles;
@PostConstruct
protected void init() {
tiles = new ArrayList<>();
if (tileResources != null) {
for (Resource itemResource : tileResources) {
TileItem tile = itemResource.adaptTo(TileItem.class);
if (tile != null && tile.hasContent()) {
tiles.add(tile);
}
}
}
}
public String getListTitle() {
return listTitle;
}
public List<TileItem> getTiles() {
return Collections.unmodifiableList(tiles);
}
public boolean hasContent() {
return listTitle != null || (tiles != null && !tiles.isEmpty());
}
}Even when a multifield has no composite="true" (i.e., it contains a single field per item), AEM stores each entry as a child resource — not as a multi-value JCR property. Each child resource has a value property containing the field's data.
Correct pattern — iterate child resources and extract the value from each:
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import javax.annotation.PostConstruct;
import org.apache.sling.api.resource.Resource;
// Inside the model class:
private List<String> tags;
@PostConstruct
private void init() {
tags = Optional.ofNullable(resource.getChild("tags"))
.map(Resource::getChildren)
.map(children -> StreamSupport.stream(children.spliterator(), false)
.map(child -> child.getValueMap().get("value", String.class))
.filter(Objects::nonNull)
.collect(Collectors.toList()))
.orElse(Collections.emptyList());
}Common shortcut (not recommended):
@ValueMapValue String[] tagsThis only works if the JCR node stores a true multi-value property (e.g.,cq:tags). For standard multifields the values are stored as child resources (tags/item0,tags/item1, …), so@ValueMapValuewill returnnull. Always use the child-resource iteration pattern above for multifield data.
package [package].models;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.DefaultInjectionStrategy;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.ChildResource;
import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;
@Model(adaptables = Resource.class, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class TileItem {
@ChildResource(name = "tileImage")
private Resource tileImageResource;
@ValueMapValue
private String tileTitle;
@ValueMapValue
private String tileDesc;
@ValueMapValue
private String tileLinkText;
@ValueMapValue
private String tileLinkURL;
public String getImageReference() {
if (tileImageResource != null) {
return tileImageResource.getValueMap().get("fileReference", String.class);
}
return null;
}
public String getTileTitle() {
return tileTitle;
}
public String getTileDesc() {
return tileDesc;
}
public String getTileLinkText() {
return tileLinkText;
}
public String getTileLinkURL() {
return tileLinkURL;
}
public boolean hasContent() {
return tileTitle != null;
}
}For complete extension guide, load: references/extending-core-components.md
| Scenario | Use Delegation? | Adaptable |
|---|---|---|
| Extending Core Component | YES | SlingHttpServletRequest |
| Need JSON export (SPA) | YES | SlingHttpServletRequest |
| Simple property-only | Optional | Resource |
@Self @Via(type = ResourceSuperType.class) for delegationSlingHttpServletRequest as adaptable (required for delegation)ComponentExporter interface for JSON exportjava.util.List collision)| Need | Java Native Alternative |
|---|---|
| String blank check | s != null && !s.trim().isEmpty() |
| String empty check | s != null && !s.isEmpty() |
| Collection empty | c == null || c.isEmpty() |
| Null-safe equals | Objects.equals(a, b) |
// Simple value
public String getTitle() {
return title;
}
// List (return unmodifiable)
public List<TileItem> getTiles() {
return Collections.unmodifiableList(tiles);
}
// Content check (REQUIRED)
public boolean hasContent() {
return title != null || (tiles != null && !tiles.isEmpty());
}import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.DefaultInjectionStrategy;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.ChildResource;
import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;
import javax.annotation.PostConstruct;
import java.util.List;
import java.util.ArrayList;
import java.util.Collections;| Component Name | Model Class | Item Class |
|---|---|---|
hero-banner | HeroBannerModel | - |
tiles-list | TilesListModel | TileItem |
card-carousel | CardCarouselModel | CardItem |
.tessl-plugin
skills
aem-rde
aem-workflow
code-assessment
content-distribution
create-component
assets
references
dispatcher
incident-response
references
dispatcher-foundation
performance-tuning
references
dispatcher-foundation
security-hardening
references
dispatcher-foundation
technical-advisory
references
dispatcher-foundation
workflow-orchestrator
references
dispatcher-foundation
ensure-agents-md
references
migration
references
dispatcher
legacy-ui
template-modernization
validation
scripts
dispatcher-tool
validate-migration