Skip to main content

Command Palette

Search for a command to run...

adaptTo() in Apache Sling: How It Works, When to Use It, and All Possible Adaptations

Updated
6 min read

In Apache Sling and AEM, adaptTo() is one of the most fundamental yet often misunderstood concepts. Almost every advanced feature in AEM like Sling Models, JCR access, services, rendering - relies on it in some way.

Understanding adaptTo() properly helps you:

  • Write cleaner APIs

  • Avoid unnecessary coupling

  • Choose the right abstraction level

  • Prevent common performance and lifecycle mistakes

This post dives deep into what adaptTo() is, how it works internally, when to use it, and all the common adaptations available in AEM.


What Is adaptTo()?

At a high level:

adaptTo() allows an object to be viewed as another type without tightly coupling the caller to the implementation.

It is Sling’s implementation of the Adapter Pattern.

<T> T adaptTo(Class<T> type);

You ask an object:

“Can you represent yourself as this type?”

If it can, you get a non-null result.
If it can’t, you get null.


Why Sling Uses adaptTo() Instead of Casting

Traditional Java (Tight Coupling)

Node node = (Node) resource; // ❌ not possible

This fails because:

  • Resource is not a Node

  • Casting assumes implementation knowledge

Sling Way (Loose Coupling)

Node node = resource.adaptTo(Node.class);

Benefits:

  • No dependency on implementation

  • Runtime flexibility

  • Cleaner APIs

  • Works across different resource types (JCR, synthetic, remote)


Where Does adaptTo() Come From?

Most Sling core objects implement:

org.apache.sling.api.adapter.Adaptable

Key Sling adaptables:

  • Resource

  • ResourceResolver

  • SlingHttpServletRequest

  • SlingHttpServletResponse


How adaptTo() Works Internally (High Level)

  1. Sling looks for a registered AdapterFactory

  2. Checks if the source → target adaptation is supported

  3. Delegates creation to the factory

  4. Returns adapted object or null

This means:

  • Adaptations are pluggable

  • New adaptations can be added without changing existing code


Most Common Adaptations in AEM

1. Resource → ValueMap

ValueMap properties = resource.adaptTo(ValueMap.class);
String title = properties.get("jcr:title", String.class);

Use when:

  • Reading node properties

  • You don’t need JCR APIs

  • You want Sling-level abstraction


2. Resource → Node

Node node = resource.adaptTo(Node.class);

Use when:

  • You need JCR-level features:

    • Versioning

    • Locking

    • Mixins

    • Low-level node operations

⚠️ Avoid unless truly needed.


3. ResourceResolver → Session

Session session = resolver.adaptTo(Session.class);

Use when:

  • Running JCR queries

  • Creating nodes programmatically

  • Performing batch operations

Best practice:

  • Always close the ResourceResolver

  • Use service users


4. SlingHttpServletRequest → Resource

Resource resource = request.getResource();

(Internally still an adaptation)

Represents:

  • The resolved content based on URL mapping

5. Resource → Sling Model

MyModel model = resource.adaptTo(MyModel.class);

Or via annotation:

@Model(adaptables = Resource.class)
public class MyModel { }

Use when:

  • Encapsulating presentation or business logic

  • Avoiding logic in HTL

  • Building reusable components


6. Request → Sling Model

MyModel model = request.adaptTo(MyModel.class);

Use when:

  • Model needs request context

  • Access to selectors, suffix, headers


7. ResourceResolver → PageManager

PageManager pageManager = resolver.adaptTo(PageManager.class);

Common AEM-specific adaptations:

  • PageManager

  • AssetManager

  • TagManager

  • UserManager

These are facades over JCR APIs, safer and higher-level.


adaptTo() in Sling Models: The Backbone of AEM Component Architecture

If there is one place where adaptTo() truly shines and becomes indispensable, it is Sling Models.

Sling Models are not a separate concept from adaptTo(), they are built entirely on top of Sling’s adaptation mechanism. Understanding this relationship is critical to writing clean, scalable, and maintainable AEM code.


Why Sling Models Exist

Before Sling Models, AEM code typically looked like this:

Resource resource = request.getResource();
ValueMap props = resource.adaptTo(ValueMap.class);
String title = props.get("jcr:title", String.class);

Problems:

  • Logic spread across servlets, JSPs, helpers

  • Repeated adaptTo() calls everywhere

  • No lifecycle management

  • Hard to test and maintain

Sling Models were introduced to:

  • Centralize adaptation logic

  • Make dependency wiring declarative

  • Reduce boilerplate

  • Encapsulate component behavior


Sling Models Are Adapter Targets

At the core, a Sling Model is simply:

A Java class that Sling knows how to adapt an object into.

@Model(adaptables = Resource.class)
public class ArticleModel {
}

This means:

ArticleModel model = resource.adaptTo(ArticleModel.class);

No magic.
Just adapter registration + dependency injection.


The Adaptation Flow (Step by Step)

Let’s walk through what happens when this line executes:

ArticleModel model = resource.adaptTo(ArticleModel.class);

Step 1: Sling Finds the Model

Sling checks:

  • Is ArticleModel annotated with @Model?

  • Does it support adapting from Resource?

If not → returns null.


Step 2: Model Factory Is Invoked

Internally, Sling uses:

ModelAdapterFactory

This factory:

  • Creates the model instance

  • Resolves dependencies

  • Manages lifecycle hooks


Step 3: Adaptable Is Bound

The adaptable object (Resource or Request) becomes the context for the model.

@Model(adaptables = Resource.class)
public class ArticleModel {

    @Self
    private Resource resource;
}

Here:

  • resource is the same object used for adaptation

Step 4: Dependency Injection via Adaptation

Each injected field is resolved using:

  • adaptTo()

  • ValueMap lookups

  • OSGi service lookups

Example:

@Inject
private Page currentPage;

Internally:

resource.adaptTo(Page.class);

Step 5: Lifecycle Hooks

After injection:

  • @PostConstruct is invoked

  • Model is considered ready

@PostConstruct
protected void init() {
    // Safe to use all injected fields
}

Without adaptTo():

  • No Sling Models

  • No clean component architecture

  • No declarative injection


adaptTo() vs Injection in Models

Manual Adaptation

MyModel model = resource.adaptTo(MyModel.class);

Declarative Injection

@Inject
private MyModel model;

Both use adaptTo() internally.
Injection just:

  • Centralizes

  • Caches

  • Manages lifecycle


Can We Create Custom Adaptations?

Yes.

You can register your own AdapterFactory:

@Component(service = AdapterFactory.class)
public class CustomAdapterFactory implements AdapterFactory {
    ...
}

Use cases:

  • Framework extensions

  • Custom domain abstractions

  • Legacy API bridging

⚠️ Advanced usage, should be done carefully.


Summary

  • adaptTo() is the core abstraction mechanism in Sling

  • Enables loose coupling and extensibility

  • Powers Sling Models, AEM APIs, and JCR access

  • Should be used intentionally and sparingly

  • Always check for null

Mastering adaptTo() means you truly understand how Sling and AEM are designed.