Resource and ResourceResolver Complete Guide in AEM
1. Introduction
Whenever we work with AEM, we are always dealing with content stored in the repository. But how exactly does AEM let us access things like pages, images, or components in the backend? That’s where we need to understand why “Resource” and “ResourceResolver” are important in AEM
Here’s what I’m going to explain, you find out in table content.
In AEM, everything you see — a page, an image, or a component is a Resource. And to interact with these resources programmatically, we need something called a ResourceResolver.
2. What’s a Resource in AEM?
Honestly, when I first heard the word “Resource” in AEM, I was a little confused. I thought, What does that mean? But over time, I realized it’s pretty simple.
In AEM, everything is called a Resource. A page is a Resource. A component is a Resource. An image, a file, a folder — all of these are Resources
When content is saved in AEM, it’s stored inside the JCR at different paths. A Resource is what AEM gives us so we can access and interact with the content at those paths
For example, say we have a page at /content/we-retail/us/en
. If I load that path as a Resource in code, I’m saying.
Hey AEM, give me the content that lives here, so I can work with it.
Once I have that Resource object in code, I can do things like:
- Read properties from it (like
jcr:title
) - Check if it has children
- Navigate to its children
- Or even adapt it into other types, like a Sling Model
3. What’s a ResourceResolver in AEM?
Now that we know what a Resource is, the next question is: how do we get a Resource into our code? Well, that’s where ResourceResolver comes in.
Think of the ResourceResolver like an interface that helps us access resources from the JCR. If you want to fetch a page, an image, or any content stored in AEM, you need a ResourceResolver to do that.
When I first learned about it, I thought of it kind of like a helper:
“Hey AEM, give me a ResourceResolver so I can look up content inside the repository.”
Once you have a ResourceResolver, you can use it to do things like:
- Get a Resource by its path (e.g.,
/content/we-retail/us/en
) - List child resources
- Navigate the content structure
- Even map URLs
ResourceResolver helps you talk to the repository and get the Resources you need. Without it, you can’t access anything inside the JCR programmatically.
4. Getting a ResourceResolver in AEM (with ResourceResolverFactory)
So now we know the ResourceResolver is what lets us access content inside AEM. But how do we get into our code?
In AEM, we usually get a ResourceResolver using something called ResourceResolverFactory.
At first, this sounded a bit complicated to me. Why do I need a factory? But it’s just a service that gives us a ResourceResolver in a safe, managed way.
Here’s what it looks like:
@Reference
private ResourceResolverFactory resourceResolverFactory;
We inject the ResourceResolverFactory
into our class (using OSGI’s @Reference
annotation), and then we can call it to get a ResourceResolver.
Usually, we need to pass in a Map of parameters when we ask for the ResourceResolver. The most common thing we pass is a subservice name, so AEM knows what permissions to give
Map<String, Object> params = new HashMap<>();
params.put(ResourceResolverFactory.SUBSERVICE, "practiceService");
ResourceResolver resolver = null;
try {
resolver = resourceResolverFactory.getServiceResourceResolver(params);
} catch (LoginException e) {
e.printStackTrace();
}
Here, "
practiceService"
refers to a service user configured in AEM to access the repository safely without using a real login.
Once we have this resolver
We can use it to fetch resources by path
Resource resource = resolver.getResource("/content/aem-debugcode/us/en/sss");
And that’s it! Now we have a Resource, thanks to the ResourceResolver.
ResourceResolverFactory helps us get a ResourceResolver, which we use to fetch resources in code.
5. System User
Service user mapping with the help of a system user allows us to create a session or resource resolver object to access a resource.
Steps to create a service user:
-
- Create a system user from CRX Explorer using the below URL, and click on log in.
http://localhost:4502/crx/explorer/index.jsp
- Create a system user from CRX Explorer using the below URL, and click on log in.

2. Click on submit after entering the User and Password will log in the user to the system.

3. Click on below User Administrative option as shown below

4. Create System User using below highlighted option in red color:

5. Provide a custom system user name as practiceUser as shown below

6. The screen below will appear after the successful creation of a system user.

7. Hit http://localhost:4502/useradmin URL and verify if the user was created successfully. UserAdmin helps us to manage users and group permissions.
Enter the created system user name and click on search. Double-click on a username.

8. Provide required permissions to read the node/resource.

9. I have provided the access read for all module and content module I have provided all access. Then click on the save button.

10. We have successfully created a user with some permissions.
6. Setting Up Service User (Service User Mapping) in AEM
We saw that to get a ResourceResolver using ResourceResolverFactory
We passed a subservice name like this
params.put(ResourceResolverFactory.SUBSERVICE, "practiceUser");
But you might wonder:
-
-
- Where does this
"
practiceUser"
come from? - Why do we need this?
- Where does this
-
Let me explain.
In AEM, for security reasons, we shouldn’t use admin sessions or real user credentials in code. Instead, Adobe recommends using Service Users — special users meant only for code to access content with limited permissions.
Think of it like this:
-
-
- A Service User is a dedicated user account for backend code, with specific permissions for what it can read/write.
- It helps avoid using admin or author privileges in code (which could be risky).
-
But creating a Service User isn’t enough — we also need to map it to the subservice name in the system. This is called Service User Mapping.
7. How to set up a Service User Mapping
We can set up Service User Mapping in two ways
-
-
- Using the OSGi Configuration Manager interface (manually configuring through
/system/console/configMgr
)
- Using the OSGi Configuration Manager interface (manually configuring through
-
2. Using a code-based configuration file (by adding .config
or .cfg.json
files in /apps/<project>/config
)
OSGi Configuration Manager interface
-
-
- Go to OSGi Configuration Manager interface — http://localhost:4502/system/console/configMgr
-
2. Search for Apache Sling Service User Mapper Service Amendment, Click on the highlighted
3. Open the highlighted and added Service Mappings.
4. Click on the save button, and search weather service mapping added to not. We will discuss what we have added in service mappings.
5. Service mapping details:
<bundleId>:<subServiceName>=<systemUser>
bundleId can be found out under the core package as artifactId or Bundle-SymbolicName, as shown below.
subServiceName is a custom service name.
systemUser created as practice user created above as part of AEM Service User.
code-based configuration file
-
-
- Go to your Project Repository Go → your-project\ui.config\src\main\content\jcr_root\apps\aem-debugcode\osgiconfig\config.
- Create a file by following this naming convention — org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended-praticeUser.xml
-
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0"
xmlns:jcr="http://www.jcp.org/jcr/1.0"
jcr:primaryType="sling:OsgiConfig"
service.ranking="100"
user.mapping="[aem-debugcode.core:practiceService\=practiceUser]"/>
3. Now, go to the Core folder inside the service folder, create PracticeUser.java interface to create a ResourceResolver instance
package com.debug.code.core.services;
import org.apache.sling.api.resource.ResourceResolver;
public interface PracticeUser {
public ResourceResolver getResourceResolver();
}
4. Create an OSGI service PracticeUserImpl.java class by implementing the PracticeUser.java interface to create a ResourceResolver instance.
package com.debug.code.core.services;
import java.util.HashMap;
import java.util.Map;
import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
@Component(service = PracticeUser.class, immediate = true)
public class PracticeUserImpl implements PracticeUser {
@Reference
ResourceResolverFactory resolverFactory;
@Override
public ResourceResolver getResourceResolver() {
ResourceResolver resolver = null;
Map<String, Object> param = getServiceParams();
try {
resolver = resolverFactory.getServiceResourceResolver(param);
} catch (LoginException e) {
e.printStackTrace();
}
return resolver;
}
public static Map<String, Object> getServiceParams() {
Map<String, Object> param = new HashMap<>();
param.put(ResourceResolverFactory.SUBSERVICE, "practiceService");
return param;
}
}
8. Getting Resource Resolver in Sling Servlet.
5. Create SlingServletByPath.java servlet class to consume created service created in the above step.
package com.debug.code.core.servlets;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import javax.servlet.Servlet;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.api.servlets.HttpConstants;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.debug.code.core.services.PracticeUser;
import org.osgi.framework.Constants;
@Component(service = Servlet.class, property = {
Constants.SERVICE_DESCRIPTION + "=Custom Servlets",
"sling.servlet.methods=" + HttpConstants.METHOD_GET,
"sling.servlet.paths=" + "/bin/practiceUser"
})
public class SlingServletByPath extends SlingAllMethodsServlet {
@Reference
PracticeUser practiceUser;
private static final long serialVersionUID = 1L;
private static final Logger logger = LoggerFactory.getLogger(SlingServletByPath.class);
@Override
protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response)
throws IOException {
ResourceResolver resourceResolver = practiceUser.getResourceResolver();
logger.info("user id: {}", resourceResolver.getUserID());
Resource contentPageResource = resourceResolver.getResource("/content/aem-debugcode/us/en/sss");
if (contentPageResource != null) {
ValueMap vm = contentPageResource.getValueMap();
Iterator<Map.Entry<String, Object>> properties = vm.entrySet().iterator();
while (properties.hasNext()) {
Map.Entry<String, Object> entry = properties.next();
logger.info("key: {}, value: {}", entry.getKey(), entry.getValue());
}
} else {
logger.error("Resource not found at /content/aem-debugcode/us/en/sss");
}
response.setStatus(SlingHttpServletResponse.SC_OK);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().print("Service Called");
}
}
6. Build code using the below command and deploy on the AEM instance using the below command.
mvn clean install -PautoInstallBundle
7. Go to this URL, Ctrl + f, and search for practiceUser will show below:
http://localhost:4502/system/console/configMgr
8. Hit the below URL to call the SlingServletByPath.java servlet class and give the below output:
http://localhost:4502/bin/practice.
9. Getting a ResourceResolver inside a Sling Model.
It mainly depends on what you’re adaptable is (whether it’s Resource.class
or SlingHttpServletRequest.class
).
In a Sling Model, you don’t directly call getResourceResolver()
from ResourceResolverFactory, like in a service/servlet. Instead, you can inject
Injecting the Resource and accessing the resolver.
When the adaptable is Resource.class
@Model(adaptables = Resource.class)
public class MyModel {
@Inject
private Resource resource;
public ResourceResolver getResolver() {
return resource.getResourceResolver();
}
}
resource.getResourceResolver()
→ Returns the resolver that was used to access that resource
Already managed by Sling → no need for manual login with subservice
Injecting ResourceResolver directly
When adaptable is Resource.class
You can get ResourceResolver
like this
@SlingObject
private ResourceReslover resourceReslover;
@SlingObject
works here because the model is adapted from a Resource
This way, it uses the resolver from the resource context.
When adaptable is SlingHttpServletRequest.class
If the adaptable is SlingHttpServletRequest.class
You can also do
@SlingObject
private ResourceResolver resourceResolver;
// or also:
@ScriptVariable
private ResourceResolver anotherResolver;
Important:
@SlingObject
Is the correct annotation for injecting ResourceResolver
@ScriptVariable
can work, but is more intended for getting context variables (like currentPage
, bindings
, etc.)
It’s better to use @SlingObject
for ResourceResolver
injection
We can use @inject also, but it does not work in all cases.
The upcoming blog will explore more with the Resource API and details.
Every great discussion starts with a simple thought! If you enjoyed this article, found it useful, or have any questions, let’s talk! I’d love to hear from you.
For more updates, tips, and engaging conversations, connect with me on Medium, LinkedIn, and RealCodeWorks. Let’s keep learning together! 🚀✨
Thank you 🙏 !