Skip to main content

Command Palette

Search for a command to run...

Thread Safety in AEM

Which Classes Are NOT Thread-Safe and Why

Updated
5 min read

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 Session is 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

  • ResourceResolver wraps:

    • 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 ResourceResolver

  • Underlying 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(), or getSession()
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.