Skip to content
AI & Agents
Skill

/resource-change-listener

AEM Cloud Service expert skill for Sling ResourceChangeListener. Covers the lightweight listener + JobConsumer pattern, migration from javax.jcr.observation.EventListener and resource-topic OSGi EventHandler, ResourceChangeListener vs ExternalResourceChangeListener decision,

From plugin
adobe-skills
162160 skills6 agents4 MCP
Install
$ npx -y skills add adobe/skills --skill resource-change-listener --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/resource-change-listener

Context preview

The summary Claude sees to decide when to auto-load this skill.

AEM Cloud Service expert skill for Sling ResourceChangeListener. Covers the lightweight listener + JobConsumer pattern, migration from javax.jcr.observation.EventListener and resource-topic OSGi EventHandler, ResourceChangeListener vs ExternalResourceChangeListener decision,

SKILL.md

resource-change-listener.SKILL.md
name: resource-change-listener
description: AEM Cloud Service expert skill for Sling ResourceChangeListener. Covers the lightweight listener + JobConsumer pattern, migration from javax.jcr.observation.EventListener and resource-topic OSGi EventHandler, ResourceChangeListener vs ExternalResourceChangeListener decision, OSGi filter configuration, review checklist, troubleshooting (silent failures, hot-path blocking, missing service-user mapping), and common pitfalls.
license: Apache-2.0

Resource Change Listener — AEM as a Cloud Service

Overview

`org.apache.sling.api.resource.observation.ResourceChangeListener` is the preferred API on AEM CS for reacting to repository content changes. The listener runs on a shared Sling thread and should return as quickly as possible.

For any blocking, repository-intensive, network, workflow, replication, indexing, asset-processing, or otherwise non-trivial work, enqueue a Sling Job and perform the processing in a `JobConsumer`.

Very small in-memory operations (metrics, counters, simple event translation, lightweight filtering, or enqueueing a job) may remain inside `onChange()` provided they do not perform repository access, network I/O, workflow operations, or expensive computation.

Two interface variants:

| Interface | Receives | Use when | |-----------|----------|----------| | `ResourceChangeListener` | Local changes only (same JVM) | Post-processing what *this* pod just wrote | | `ExternalResourceChangeListener` (extends RCL) | Local **and** external (other cluster nodes) | Cluster-wide reactions: cache invalidation, replication follow-ups |

Three CS-specific constraints every listener must satisfy:

| Constraint | Why | |-----------|-----| | No `ResourceResolver` / `Session` / JCR ops inside `onChange()` | Blocks the shared listener thread; delays every other registered listener | | `getServiceResourceResolver(SUBSERVICE)` in the consumer | `getAdministrativeResourceResolver` is removed from the CS SDK | | Filter via `PATHS` / `CHANGES` OSGi properties — not in code | Sling delivers only matching events; in-code filtering wastes the listener thread |

> **`PATHS` are chosen by the implementer from the business scope.** Do not ask the customer for raw `PATHS` syntax (`/content/dam`, `glob:/**/jcr:content/*`, etc.) unless the business scope itself is unclear. Derive the path from what the listener is supposed to react to.

---

Classification — choose before making any changes

**Already implements `ResourceChangeListener`** and `onChange()` only enqueues jobs → Already compliant — verify against the [Review Checklist](#review-checklist) only.

**Already implements `ResourceChangeListener`** and `onChange()` contains business logic (resolver, JCR ops, heavy processing) → Apply **R1–R5** (skip R0).

**Implements `javax.jcr.observation.EventListener`** or **`EventHandler`** subscribed to `org/apache/sling/api/resource/Resource/*` → Apply **R0 then R1–R5**.

**Implements `EventHandler`** subscribed to replication / workflow / custom topics → Use the `event-migration` skill instead — not this one.

**One pattern per session.** If the bundle has multiple legacy listeners, migrate one class at a time.

**Before starting:** Read [`../references/aem-cloud-service-pattern-prerequisites.md`](../references/aem-cloud-service-pattern-prerequisites.md) and apply SCR→DS, service-user, and SLF4J fixes if present in the same changeset.

---

Discovery

Detection is performed by the analyzer ([`../scripts/analyze.sh`](../scripts/README.md)), run by the runbook:

bash ../scripts/analyze.sh <workspace-root> --pattern resource-change-listener

**Match criteria (what the detector flags):** a class that **`implements org.apache.sling.api.resource.observation.ResourceChangeListener`** or **`ExternalResourceChangeListener`** (import-aware) — the modern API, flagged for review against the lightweight + JobConsumer contract.

Emitted at the class declaration, with the class header as the snippet. Parse-level only — direct `implements` clause; reached-via-base-class is not resolved. Legacy `javax.jcr.observation.EventListener` is detected by the `event-migration` pattern (matching the BPA subtype taxonomy); when its logic is plain content observation, that guide routes it back here. A class implementing both `ResourceChangeListener` and an event interface is flagged by both patterns (rare).

Resolution contract

**guided** — `apply (guided)`. The analyzer locates each `ResourceChangeListener`; remediation is judgment-based and applied via R0–R5 (per the Classification above) in an apply session.

| Site shape | Disposition | |---|---| | `implements ResourceChangeListener`, `onChange()` does repository/JCR/heavy work | apply (guided) → R1–R5 | | Legacy `javax.jcr.observation.EventListener` / resource-topic `EventHandler` routed here *(arrives via `event-migration` redirect or migration handoff — this pattern's own detector flags only the modern API)* | apply (guided) → R0 then R1–R5 | | `implements ResourceChangeListener`, `onChange()` only enqueues a Sling Job | skipped: `already-compliant` | | Test code (`src/test/`) | skipped: `test-scope` |

---

Complete example — before and after

Before (legacy JCR `EventListener` with inline logic and admin resolver)

package com.example.listeners;

import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;

import javax.jcr.observation.Event;
import javax.jcr.observation.EventIterator;
import javax.jcr.observation.EventListener;

@Component(immediate = true)
@Service
public class ACLPolicyListener implements EventListener {

    @Reference
    private ResourceResolverFactory resolverFactory;

    @Override
    public void onEvent(EventIterator events) {
        try {
Read more
Ships withadobe-skills

Repository of Adobe skills for AI coding agents.

Get the whole plugin

Other skills on adobe-skills.