# Service Users and ResourceResolvers: Accessing the Repository Safely

Sooner or later, your backend code needs to read or write content: look up a page, update a property, create a report node. How you get that access matters for security, for stability and, on AEM as a Cloud Service, for whether your build passes. This post explains **service users**, how to map them to your code, how to give them exactly the permissions they need, and how to use and close a `ResourceResolver` correctly.

## The Rule: No Admin Sessions

In older AEM code you may have seen `loginAdministrative()` or `getAdministrativeResourceResolver()`. These give your code full access to everything. They are deprecated, and they are not a safe pattern on any version. On Cloud Service, use a **service user** instead.

A service user is a system account that:

- Has **no password** and cannot be used to log in
- Has only the **permissions you grant**
- Is **mapped** to a specific bundle and sub-service name

## Three Pieces of Setup

### 1\. Create the service user and permissions with repoinit

**Repoinit** is a small declarative language that runs at startup and sets up users, folders and permissions. Put the script in an OSGi configuration:

`org.apache.sling.jcr.repoinit.RepositoryInitializer~myproject.cfg.json`

```json
{
  "scripts": [
    "create service user myproject-content-reader with path system/myproject",
    "set ACL for myproject-content-reader",
    "  allow jcr:read on /content/myproject",
    "end"
  ]
}
```

Only grant what the code needs. A reader gets `jcr:read` on its content path. A writer also gets `jcr:modifyProperties` or `jcr:write` on the **one** path it updates.

### 2\. Map the service user to your bundle

`org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended~myproject.cfg.json`

```json
{
  "user.mapping": [
    "myproject.core:content-reader=[myproject-content-reader]"
  ]
}
```

The format is `bundle-symbolic-name:subservice-name=[user]`. The bundle name comes from your `core` module, and `content-reader` is a name you choose. The brackets refer to the service user's principal name, which for a user created by repoinit is the same as its name.

### 3\. Use it in code

```java
@Component(service = PageReportService.class)
public class PageReportServiceImpl implements PageReportService {

    private static final Logger LOG = LoggerFactory.getLogger(PageReportServiceImpl.class);

    @Reference
    private ResourceResolverFactory resolverFactory;

    @Override
    public String titleOf(String path) {
        Map<String, Object> auth =
                Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, "content-reader");

        try (ResourceResolver resolver = resolverFactory.getServiceResourceResolver(auth)) {
            Resource page = resolver.getResource(path + "/jcr:content");
            return page == null ? null : page.getValueMap().get("jcr:title", String.class);
        } catch (LoginException e) {
            LOG.error("Could not open service resolver for sub-service 'content-reader'", e);
            return null;
        }
    }
}
```

## Close What You Open

A `ResourceResolver` from the factory holds system resources. You must close it. On Cloud Service this is not just good manners: **Cloud Manager's code quality check flags unclosed** `ResourceResolver` **objects**, and a failed check can stop your pipeline.

The simplest way is **try-with-resources**, as above, because `ResourceResolver` is `Closeable`. Two common misunderstandings:

- "A resolver created from an existing JCR session doesn't need closing." It does
- "Closing it closes the underlying session." It doesn't. Close it regardless of how it was opened

## When *Not* to Open a Resolver

If code already has a resolver for the current request, **use that one** and don't close it:

- In a **Sling Model** adapted from a request or resource, inject `@SlingObject ResourceResolver`, which uses the **current user's permissions** and is managed for you
- In a **servlet**, use `request.getResourceResolver()`

Use a service user only when the work legitimately needs more access than the current user has, or when there's no user at all, as in a background job or event handler. Reaching for a service resolver "to make it work" can hide a permissions problem, and it bypasses the user's access controls, so you become responsible for authorization.

## Writing Content

```java
ModifiableValueMap props = resource.adaptTo(ModifiableValueMap.class);
if (props != null) {
    props.put("lastAudited", Calendar.getInstance());
    resolver.commit();   // throws PersistenceException
}
```

Keep these habits:

- **Commit once** after related changes, not after every property
- Catch `PersistenceException` and log it with the path
- Don't write to `/apps` or `/libs`. On Cloud Service they're **immutable at runtime**. Write only to mutable areas like `/content`, `/conf` or `/var`
- Create the target folder (for example `/var/myproject`) with a repoinit line rather than in code:

```plaintext
create path /var/myproject(sling:Folder)
```

## AEM as a Cloud Service vs 6.5

| Topic | AEM 6.5 | AEM as a Cloud Service |
| --- | --- | --- |
| Admin sessions | Deprecated but may still work | Don't use them. Build with service users |
| Creating the user | Often via CRXDE, a package or the user admin console | **Repoinit** in your code project, applied on every deployment |
| Mapping | Config Manager or a config file | A config file in `ui.config` |
| Runtime writes to `/apps` | Possible | Not possible: `/apps` and `/libs` are immutable |
| Missing ACLs | Fixed by hand on the instance | Fixed in code and redeployed |

The key difference is that on Cloud Service **everything about the user is code**. That's good for repeatability: Dev, Stage and Production get the same users and permissions from the same files.

## Common Pitfalls

- `LoginException` **at runtime.** The mapping doesn't match: wrong bundle symbolic name, wrong sub-service name, or the repoinit user doesn't exist yet. Check the logs for the exact name it tried
- `null` **resources for paths you know exist.** The service user can't read them. Check the ACL
- **Mapping uses the wrong bundle name.** The prefix must be your bundle's **Bundle-SymbolicName**, as shown in the Developer Console
- **Resolver leaked in a loop.** Open one outside the loop, not one per iteration
- **Using one powerful service user everywhere.** Prefer several narrow ones, such as a reader and a writer

## What's Next

Now that your services can read and write content safely, the next post exposes them over HTTP with **Sling servlets**: how to register them, why resource types beat paths, and how to keep them secure.

---

*Next in this series: \[Sling Servlets: Registration, Resource Types and Security\]*
