Skip to main content

Command Palette

Search for a command to run...

How to Use Non-Thread-Safe AEM Objects Safely

Updated
4 min read

JCR and Sling objects are contextual views, not reusable services.

They represent:

  • A user

  • A request

  • A moment in time

So the rule is simple:

Acquire late → Use briefly → Release early


1. ResourceResolver (The Most Misused Object)

❌ What NOT to Do

@Component
public class BadService {
    private ResourceResolver resolver; // ❌ never
}
  • Breaks thread safety

  • Breaks permissions

  • Causes memory leaks


✅ Correct Pattern

try (ResourceResolver resolver =
        resolverFactory.getServiceResourceResolver(authInfo)) {

    Resource resource = resolver.getResource("/content/site");

}

Why This Is Safe

  • New resolver per execution

  • Correct security context

  • Auto-closed

  • No shared state


2. JCR Session (Never Cache, Never Share)

❌ Wrong

private Session session; // ❌
Session session = resolver.adaptTo(Session.class);
try {
    Node node = session.getNode("/content/site");
} finally {
    session.logout(); // or close resolver
}

Rule:

The session must die with the request or operation.


3. Sling Models (Use, Don’t Store)

❌ Wrong

static MyModel model; // ❌

✅ Right

MyModel model = resource.adaptTo(MyModel.class);
String title = model.getTitle(); // extract data

Rule

Convert models into plain data ASAP.


4. Servlets (Stateless Only)

❌ Unsafe Servlet

@Component
public class BadServlet extends SlingAllMethodsServlet {
    private List<String> cache = new ArrayList<>(); // ❌
}

✅ Safe Servlet

protected void doGet(...) {
    List<String> cache = new ArrayList<>(); // local
}

Rule

Servlet fields must be immutable or constants.


5. OSGi Services (Design for Concurrency)

❌ Dangerous Service

@Component
public class BadService {
    private ResourceResolver resolver; // ❌
}

✅ Safe Service

@Component
public class SafeService {

    @Reference
    private ResourceResolverFactory resolverFactory;

    public void execute() {
        try (ResourceResolver resolver =
                resolverFactory.getServiceResourceResolver(auth)) {
            // work
        }
    }
}

Rule

Services may acquire contextual objects, but never store them.


6. QueryBuilder (Per Resolver, Per Query)

❌ Wrong

private Query query; // ❌

✅ Right

Query query = queryBuilder.createQuery(predicates, session);
SearchResult result = query.getResult();

Discard after use.


7. Page, Asset, Resource Objects

❌ Wrong

private Page page; // ❌

✅ Right

Page page = pageManager.getPage(path);
String title = page.getTitle();

Extract → discard.


8. ValueMaps & ModifiableValueMaps

❌ Wrong

private ValueMap properties; // ❌

✅ Right

ValueMap vm = resource.getValueMap();
String title = vm.get("jcr:title", String.class);

9. Use Factory Patterns, Not Singletons

Example: Session Factory

public Session getSession() {
    ResourceResolver rr = resolverFactory.getServiceResourceResolver(auth);
    return rr.adaptTo(Session.class);
}

Not cached. Ever.


Correct Architecture Pattern (AEM-Proven)

Layered Design

Servlet / Model
   ↓
Service (stateless)
   ↓
Repository Access (scoped)

Each layer:

  • Acquires its own context

  • Releases it immediately


Checklist (Memorize)

  • ❓ Am I storing a resolver/session/model in a field?

  • ❓ Am I sharing objects across requests?

  • ❓ Does this object come from adaptTo()?

  • ❓ Does this object need closing?


💡
In AEM, objects are cheap to create and expensive to share.

Final Summary

Object Safe Usage
ResourceResolver Create per operation, always close
Session Never cache, never share
Sling Model Use and discard
Servlet Stateless only
OSGi Service Stateless by design
QueryBuilder One query per resolver
Page / Asset Extract data, discard