Best practices for AEM 6.5 LTS developers covering architecture, security, performance, and coding standards.
Best Practices For Aem Developer
Architecture & Component Design
Use Sling Models Over WCMUsePojo
Prefer @Model-annotated Sling Models with @Inject / @ValueMapValue / @ChildResource. Avoid WCMUsePojo — it is a legacy approach and does not support proper unit testing with AEM Mocks.
Write Logic in HTL, Not JSP
AEM 6.5 uses HTL (HTML Template Language) as the primary templating engine. JSP is deprecated for new development. Keep HTL free of business logic — delegate to Sling Models via data-sly-use.
Follow the Proxy Component Pattern
Always proxy Core Components instead of copying them. Extend only what differs:
This ensures you inherit Core Component updates and security patches without overriding base code.
OSGi & Configuration
Use DS Annotations (Not Felix SCR)
AEM 6.5 uses OSGi R6/R7 Declarative Services. Use @Component, @Activate, @Deactivate from org.osgi.service.component.annotations — not the deprecated Felix @org.apache.felix.scr.annotations.*.
Externalise Configuration via OSGi Config Files
Never hardcode environment-specific values. Use OSGi configuration files per run mode:
Cloud Services vs. OSGi Config
Use Cloud Services (via /conf) for integration credentials (e.g., reCAPTCHA site keys, analytics IDs) so authors can manage them per site. Reserve OSGi configs for infrastructure-level settings developers control.
Sling & JCR
Prefer Resource API Over JCR API
Use Resource, ValueMap, and ResourceResolver instead of raw JCR Node/Session wherever possible. The Sling abstraction is mock-friendly and decoupled from the repository implementation.
Always Close ResourceResolver
Resource resolvers opened programmatically must be closed in a finally block or try-with-resources to prevent repository session leaks:
Use Service Users, Not Admin Sessions
ResourceResolverFactory.getAdministrativeResourceResolver() is disabled by default in AEM 6.5. Create a system user with minimum required ACLs and map it via org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended.
Node Naming
Use lowercase, hyphenated names for JCR nodes (e.g., my-component, not MyComponent). Avoid spaces and special characters.
Servlets
Prefer Resource-Type & Selector Servlets Over Path-Based
Path-based servlets bypass Dispatcher caching and require explicit allow rules. Bind servlets by resourceType + selector + extension:
Security
Sanitise Output in HTL
HTL applies automatic XSS escaping based on context. Always specify the correct context explicitly when in doubt:
Never use @ context='unsafe' unless the content is fully controlled and sanitised server-side.
Validate on the Server Side
Client-side validation is UX only. Always re-validate inputs on the server (Sling servlet or service layer) before persisting to JCR.
Restrict Servlet Access
Use Dispatcher allowedSelectors / allowedExtensions filters to block unexpected selector or extension combinations that could expose unintended servlet endpoints.
Performance & Caching
Design for Dispatcher Cache
Dispatcher caches full HTML pages. Keep pages cacheable by default:
- Avoid session-dependent or user-specific content in page HTML.
- Use Sling Dynamic Include (SDI) for personalised or time-sensitive fragments within otherwise cacheable pages.
- Use AEM selectors (e.g.,
.model.json) instead of query parameters — query strings invalidate Dispatcher cache by default.
JCR Query Optimisation
- Always create Oak indexes for properties used in queries (
/oak:index). - Prefer property-based indexes (
luceneorpropertytype) overnodetypetraversal. - Use
QueryBuilderorQueryAPIs — never string-concatenate JCR-SQL2/XPath with user input (SQL injection risk). - Monitor slow queries via
/system/console/queryDebugLog.
Avoid Large Result Sets
Paginate JCR queries. Never iterate unbounded result sets in request threads — use background jobs (Sling Jobs) for bulk operations.
Content & Editable Templates
Use Editable Templates, Not Static
AEM 6.5 fully supports Editable Templates stored under /conf. Avoid static templates in /apps for new projects — they do not support policy-driven component control or initial content per template.
Lock Down Component Policies
Use the Template Editor to whitelist allowed components per parsys. This prevents authors from inserting incompatible or unstyled components.
Logging & Error Handling
Use SLF4J, Not System.out
Set package-level log levels in OSGi (/system/console/slinglog) without redeploying code.
Never Swallow Exceptions
Always log the full exception, not just the message:
Testing
Use AEM Mocks
Unit-test Sling Models and servlets with AemContext from io.wcm.testing.mock.aem: