> 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.

# 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.

```java
@Model(adaptables = Resource.class, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class MyComponentModel {

    @ValueMapValue
    private String title;

    @ChildResource
    private Resource items;
}
```

### 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`.

```html
<sly data-sly-use.model="com.example.models.MyComponentModel">
    <h2>${model.title @ context='text'}</h2>
</sly>
```

### Follow the Proxy Component Pattern

Always proxy Core Components instead of copying them. Extend only what differs:

```
/apps/my-project/components/content/mybutton
  sling:resourceSuperType = core/wcm/components/button/v2/button
```

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.*`.

```java
@Component(service = MyService.class, immediate = true)
public class MyServiceImpl implements MyService {

    @Activate
    protected void activate(ComponentContext ctx) { ... }
}
```

### Externalise Configuration via OSGi Config Files

Never hardcode environment-specific values. Use OSGi configuration files per run mode:

```
/apps/my-project/config.author/com.example.MyServiceImpl.cfg.json
/apps/my-project/config.publish/com.example.MyServiceImpl.cfg.json
```

### 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:

```java
try (ResourceResolver resolver = resolverFactory.getServiceResourceResolver(params)) {
    // use resolver
}
```

### 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`:

```java
@Component(service = Servlet.class)
@SlingServletResourceTypes(
    resourceTypes = "my-project/components/content/search",
    selectors = "results",
    extensions = "json",
    methods = HttpConstants.METHOD_GET
)
public class SearchResultsServlet extends SlingSafeMethodsServlet { ... }
```

***

## Security

### Sanitise Output in HTL

HTL applies automatic XSS escaping based on context. Always specify the correct context explicitly when in doubt:

```html
${user.bio @ context='text'}       <!-- plain text, strips HTML -->
${page.url @ context='uri'}        <!-- URL encoding -->
${component.html @ context='html'} <!-- trusted HTML only -->
```

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 (`lucene` or `property` type) over `nodetype` traversal.
- Use `QueryBuilder` or `Query` APIs — 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`

```java
private static final Logger log = LoggerFactory.getLogger(MyClass.class);
log.debug("Processing resource: {}", resource.getPath());
```

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:

```java
} catch (RepositoryException e) {
    log.error("Failed to read node at {}", path, e); // pass Throwable as last arg
}
```

***

## Testing

### Use AEM Mocks

Unit-test Sling Models and servlets with `AemContext` from `io.wcm.testing.mock.aem`:

```java
@ExtendWith(AemContextExtension.class)
class MyModelTest {

    private final AemContext ctx = new AemContext();

    @Test
    void testTitleIsReturned() {
        ctx.currentResource(ctx.create().resource("/content/test",
            "sling:resourceType", "my-project/components/content/mycomp",
            "title", "Hello"));
        MyComponentModel model = ctx.request().adaptTo(MyComponentModel.class);
        assertEquals("Hello", model.getTitle());
    }
}
```

***

## Summary

| Area       | Do                                 | Avoid                                  |
| ---------- | ---------------------------------- | -------------------------------------- |
| Templating | HTL + Sling Models                 | JSP, WCMUsePojo                        |
| OSGi       | DS Annotations (`org.osgi.*`)      | Felix SCR annotations                  |
| JCR access | Service users, `ResourceResolver`  | Admin sessions, raw `Node` API         |
| Servlets   | Resource-type + selector binding   | Path-based servlets                    |
| Caching    | Dispatcher-friendly selectors, SDI | Query parameters, session-scoped pages |
| Testing    | AEM Mocks, JUnit 5                 | Manual CRXDE testing only              |
| Config     | OSGi `.cfg.json` per run mode      | Hardcoded environment values           |
