How to Use Non-Thread-Safe AEM Objects Safely
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; // ❌
✅ Right
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?
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 |

