Thread Safety in AEM
Which Classes Are NOT Thread-Safe and Why
Thread safety in AEM means ensuring that request-scoped objects (such as ResourceResolver, Session, and Resource) are never shared across threads. AEM handles many requests concurrently, and many core APIs are not designed for concurrent access.
This post explains which commonly used AEM, Sling, and JCR classes are not thread-safe, why sharing them across requests is dangerous, and how many real-world production issues trace back to misunderstanding these fundamentals.
First, the Golden Rule
If an object represents request state, repository state, or mutable content - assume it is NOT thread-safe.
AEM is:
Highly concurrent
Multi-threaded
Request-driven
Thread safety violations cause:
Data corruption
Random bugs
Production-only failures
1. JCR API (NOT Thread-Safe)
Absolutely NOT thread-safe
These are the most dangerous to misuse.
Core JCR Objects
| Class | Thread Safe? | Why |
|---|---|---|
javax.jcr.Session |
❌ No | Holds transient state |
javax.jcr.Node |
❌ No | Mutable, session-bound |
javax.jcr.Property |
❌ No | Session state |
javax.jcr.Value |
❌ No | Repository-backed |
javax.jcr.Workspace |
❌ No | Session-scoped |
Why
A
Sessionis tied to:Authentication
Permissions
Transient changes
Nodes are views of a session snapshot
Sharing breaks isolation
Very Common Bug
@Component(service = MyService.class)
public class MyService {
private Session session; // ❌ DANGEROUS
@Activate
void activate(Session session) {
this.session = session;
}
}
This will eventually corrupt state.
2. Sling Resource API (NOT Thread-Safe)
Sling Objects Bound to Requests
| Class | Thread Safe? |
|---|---|
ResourceResolver |
❌ No |
Resource |
❌ No |
ValueMap |
❌ No |
ModifiableValueMap |
❌ No |
Why
ResourceResolverwraps:JCR session
Security context
Caching
Closing is mandatory
Sharing breaks permissions
❌ Anti-pattern
@Component
public class BadService {
@Reference
private ResourceResolver resolver; // ❌ WRONG
}
A ResourceResolver must never be injected or cached.
3. Sling Models (NOT Thread-Safe)
All Sling Models Are Request-Scoped
Instantiated per request
Backed by mutable data
Injected values are contextual
Unsafe to Share
@Model(adaptables = Resource.class)
public class MyModel {
private String title;
}
Never store models in:
Static fields
Singleton services
Caches
4. Servlets (Condition-Based)
Servlet Instances ARE Shared
| Type | Thread Safe? |
|---|---|
| Sling Servlet | ⚠️ Depends |
| Servlet Fields | ❌ No |
The Servlet Object
Created once
Used by multiple threads
❌ Unsafe
@Component(service = Servlet.class)
public class MyServlet extends SlingSafeMethodsServlet {
private int counter; // ❌ shared mutable state
}
✅ Safe
protected void doGet(...) {
int counter = 0; // local variable → safe
}
5. OSGi Services (Developer Responsibility)
By Default
OSGi services are singletons
Shared across threads
Thread Safety Depends On:
| Service Type | Thread Safe? |
|---|---|
| Stateless services | ✅ Yes |
| Stateful services | ❌ No |
| Services holding JCR/Sling objects | ❌ No |
❌ Dangerous Service
@Component
public class BadService {
private ResourceResolver resolver; // ❌
}
6. QueryBuilder & Search APIs (NOT Thread-Safe)
| Class | Thread Safe? |
|---|---|
QueryBuilder |
❌ |
Query |
❌ |
SearchResult |
❌ |
Why:
Bound to
ResourceResolverUnderlying session state
7. WCM & DAM APIs (Mostly ❌)
| API | Thread Safe? |
|---|---|
Page |
❌ |
PageManager |
❌ |
Asset |
❌ |
AssetManager |
❌ |
These are:
Resource-backed
Session-aware
Mutable
8. What IS Thread-Safe in AEM (✅)
To balance things, here’s what is safe.
Generally Safe
| Category |
|---|
| Utility classes |
| Value objects |
| Constants |
| Immutable DTOs |
| OSGi Configurations |
String, Optional, primitives |
| Apache Commons utilities |
Safe OSGi Services
Stateless
No shared mutable state
No request-bound objects
9. Correct Patterns (How to Stay Safe)
Pattern 1: Always Acquire Per Request
try (ResourceResolver resolver =
resolverFactory.getServiceResourceResolver(...)) {
// use resolver
}
Pattern 2: Convert Early, Release Fast
String path = resource.getPath(); // extract data
// discard resource
Pattern 3: Pass Data, Not Objects
❌ Pass:
Node
Resource
Session
✅ Pass:
String path
DTO
ID
10. One Rule to Remember Forever
If it has
getResourceResolver(),adaptTo(), orgetSession()—
it is NOT thread-safe.
11. Why This Matters More in AEM Cloud
Higher concurrency
Autoscaling
Request spikes
No ability to “restart and fix”
Thread-safety bugs only appear under load.
Final Summary (Memorize This)
JCR objects → ❌ Not thread-safe
Sling objects → ❌ Not thread-safe
Sling Models → ❌ Not thread-safe
Servlets → ⚠️ Only if stateless
OSGi services → ⚠️ You must design them thread-safe
Next
In the next post, we’ll focus on the right way to use these objects, exploring proven patterns and best practices for using non-thread-safe AEM objects safely in real-world applications.

