All AEM as a Cloud Service skills: component development, Workflow model design, development, triggering, launchers, debugging, and triaging, Dispatcher config authoring, advisory, incident response, performance tuning, and security hardening, Rapid Development Environment (RDE) deployment, log inspection, snapshots, and troubleshooting via `aio aem rde`, Jackrabbit Oak query/index tuning (property, Lucene index definitions), and extracting/inventorying JCR queries (XPath, SQL2, QueryBuilder) across a codebase.
85%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Low
Low-risk findings worth noting
Read this fully before editing. Control plane: SKILL.md.
Detection is plugin-driven: the AEM Analyser Maven Plugin (
com.adobe.aem:aemanalyser-maven-plugin, latest release on Maven Central) is the source of truth for what is deprecated and what to replace it with. Every fix flows from a finding'shintfield (the analyser'sdeprecated.msg). No hardcoded rule tables live in this recipe — the plugin's output IS the ruleset, refreshed on every run.
Apache Maven 3.x or newer with a supported JVM must be available. Verify:
java -version
mvn --version 2>&1 | head -5If the project uses a Maven wrapper, prefer it:
[ -f ./mvnw ] && ./mvnw --version 2>&1 | head -5Set the Maven command variable — used in all build steps below:
MVN_CMD = "./mvnw" if mvnw exists, otherwise "mvn"CRITICAL: Maven commands produce very verbose output that can exceed the tool output buffer limit (1 MB). Every Maven invocation redirects to a log file and only returns the tail:
$MVN_CMD <args> > /tmp/mvn-modernize.log 2>&1; \
MVN_EXIT=$?; \
echo "=== Maven exit code: $MVN_EXIT ==="; \
tail -150 /tmp/mvn-modernize.log; \
exit $MVN_EXITFull output stays available in /tmp/mvn-modernize.log.
Findings come from the shared analyzer (analyze.sh) in the standard shape, with an
added hint field carrying the AEM Analyser Maven Plugin's deprecation message for
that package:
{
"findings": [
{"pattern": "remove-deprecated-api",
"file": "core/src/main/java/com/example/MyService.java",
"line": 5,
"snippet": "org.apache.log4j.Logger",
"hint": "The log4j 1.x libraries are deprecated. Please use org.slf4j instead."}
],
"warnings": []
}The snippet is the fully-qualified imported package (as reported by the Java
compiler tree). Sub-category is inferred from the file extension: .java → Java
import; pom.xml → Maven dependency; .cfg / .cfg.json / .config → OSGi config.
The current Java detector emits findings for Java imports only; the recipe's Step 4
also inspects the rules cache and the project's pom.xml for Maven-dep matches
against the same deprecated packages (see Step 4d Category B).
Sources:
detect.sh) so the analyzer has a fresh rules cache to
consume.SKILL.md.Migration Progress:
- [ ] Step 1: Pre-flight (verify Maven and JVM, set MVN_CMD)
- [ ] Step 2: Initial build with tests (verify compile; set SKIP_TESTS_FLAG test policy)
- [ ] Step 3: Detection — 3a preflight (detect.sh, populates rules cache); 3b analyzer (analyze.sh consumes cache)
- [ ] Step 4: Apply edits (each fix driven by the finding's hint; Experience League fallback)
- [ ] Step 5: Post-transformation build (verify edits didn't break anything)
- [ ] Step 6: AI-assisted code fixes (fix compile errors introduced by rewrites)
- [ ] Step 7: Final build verification (analyser now clean)
- [ ] Step 8: Generate reportSteps 2, 5, and 7 are build steps. Step 2 verifies the project compiles before any changes. Step 5 catches breakage introduced by Step 4 edits. Step 7 is the final verification after AI-assisted fixes. Do not carry forward module skips or assumptions between build steps — transformations and fixes in between may change which modules compile:
Test policy is decided once, in Step 2, and carried forward via SKIP_TESTS_FLAG:
Step 2 runs the baseline build with tests. Its outcome sets SKIP_TESTS_FLAG:
SKIP_TESTS_FLAG = "" (empty).SKIP_TESTS_FLAG = "-DskipTests".Steps 5 and 7 substitute $SKIP_TESTS_FLAG in place of any hardcoded -DskipTests.
Do not override this decision mid-run.
Step 4 (edits) → may fix some modules but break others (successor class has a different signature than the deprecated one).
Step 6 (AI fixes) → resolves compilation errors from Step 5.
Step 7 (final) → captures the true state and re-runs the analyser to confirm zero past-due deprecations.
Always attempt the full project build at each build step.
Build the project with tests to verify it compiles — and to decide the test policy
for every later build. Note the absence of -DskipTests: this build must exercise the
test suite so its outcome can set SKIP_TESTS_FLAG (see "How build steps relate").
$MVN_CMD clean install \
-DskipFrontend=true \
-Dassembly.skipAssembly=true \
-Dcheckstyle.skip=true \
-Dvault.skipValidation=true \
-Dsling.install.skip=true \
-Dexec.skip=true \
-Dpackage.skip=true \
-Dosgi.bundle.status.skip=true \
-Djacoco.skip=true \
> /tmp/mvn-modernize.log 2>&1; \
MVN_EXIT=$?; \
echo "=== Maven exit code: $MVN_EXIT ==="; \
tail -150 /tmp/mvn-modernize.log; \
exit $MVN_EXITIf exit code = 0 (build passed with tests running): set SKIP_TESTS_FLAG = "" —
tests are healthy, so running them is mandatory in Steps 5 and 7. Proceed to Step 3.
If exit code != 0: read the log and classify the failure before acting.
maven-surefire-plugin (or failsafe) in the
failing goal, a Tests run: … Failures: … Errors: … summary with non-zero
Failures/Errors, and no COMPILATION ERROR / maven-compiler-plugin failure.
These are pre-existing failures unrelated to the migration → set
SKIP_TESTS_FLAG = "-DskipTests" and proceed to Step 3 (do not retry, do not
STOP). Note in the report that baseline tests were failing and later builds skip
tests.COMPILATION ERROR / maven-compiler-plugin): Fix and
re-run. Retry up to 3 times total. If the re-run then fails on tests only, apply the
test-failure rule above.If still failing on compilation after 3 attempts: STOP. Report: "Project does not compile before migration. Fix the baseline build first."
detect.sh (populates the rules cache)bash "$SKILL_DIR/remove-deprecated-api/scripts/detect.sh" "$PROJECT_ROOT"$SKILL_DIR is plugins/aem/cloud-service/skills/code-assessment. detect.sh:
aemanalyser-maven-plugin and the latest aem-sdk-api
versions from Maven Central (each pin-able with --pin-plugin / --pin-sdk).
The latest SDK is forced via -DsdkVersion=<latest> -DsdkUseDependency=false so
the freshest deprecation metadata is used — this matches Cloud Manager and
overrides any <sdkVersion> / <useDependencyVersions> pin in the plugin
config without touching the pom (pass --respect-pom-sdk to keep the pin).com.adobe.aem:aemanalyser-maven-plugin:<version>:project-analyse coordinates —
mvn package com.adobe.aem:aemanalyser-maven-plugin:<version>:project-analyse ….
The resolved version runs whether or not the project declares the plugin, so the
pom is never modified (no patch, no backup, no restore). The package phase
builds the artifacts project-analyse inspects. Log captured at
/tmp/aem-analyser.log.Usage of deprecated ... found : <pkg> : <hint> ... lines from the log.<package>\t<hint>\t<for_removal> per line —
at $AEM_DEPRECATED_API_RULES (env override) or the default path
$TMPDIR/aem-code-assessment/deprecated-api-rules.tsv.If detect.sh exits non-zero — inspect the reason:
com.adobe.aem:aem-sdk-api as a parent BOM or dependency") and skip.Pin the plugin version with --pin-plugin <version> when the environment
requires a specific release (e.g. air-gapped mirrors). Otherwise the latest release
is always chosen.
bash "$SKILL_DIR/scripts/analyze.sh" "$WORKSPACE_ROOT" --pattern remove-deprecated-apiThe Java RemoveDeprecatedApi detector reads the rules TSV from the well-known cache
path, matches each rule's package against every import in the parsed Java corpus
(longest-prefix), applies today's past-due gating (defence-in-depth against a stale
cache), and emits findings in the standard shape with an added hint field:
{"pattern":"remove-deprecated-api","file":"core/src/main/java/.../MyService.java",
"line":5,"snippet":"org.apache.log4j.Logger","hint":"...Please use org.slf4j instead."}If the rules cache is missing (Phase 1 was skipped or failed), the detector
emits a single warning
deprecated-api-rules-missing: expected TSV at <path> — run remove-deprecated-api/scripts/detect.sh preflight first
and produces no findings for this pattern. Other detectors continue unaffected.
Every subsequent step operates on the analyzer's findings list.
Work through the findings list, grouped by unique package. For each group, derive
the fix from the finding's hint (never from a hardcoded table). Group edits by
sub-category — Java imports first, then Maven dependencies, then OSGi configs.
Apply these phrase rules to the hint field, in order (first match wins):
| Phrase in hint | Interpretation |
|---|---|
"Please use X instead" / "Use X instead" | X is the drop-in successor package |
"X should be used" | X is the successor |
"The <Lib> libraries are deprecated" (no successor named) | Fall back to Experience League |
"Usage of this API is not supported in AEM as a Cloud Service" | Fall back to Experience League |
"This internal <Z> API is not supported" | Use only the public API of Z; drop the .spi / .internal package |
"This version of the <Lib> library is deprecated. Use your own version" | Explicit Maven dep required — get user consent |
| Hint empty or unrecognised | Fall back to Experience League |
Regex hints (for machine extraction):
Please use ([a-z][\w]*(?:\.[a-z][\w]*)+) insteadUse ([a-z][\w]*(?:\.[a-z][\w]*)+) instead([a-z][\w]*(?:\.[a-z][\w]*)+) should be usedA candidate successor must have at least two dot-separated segments — sentence fragments like "Use JDK instead" are not valid packages.
When the hint names no successor, consult Adobe's canonical API Removal Guidance
page once per session via WebFetch:
Extract the recommended successor (or explicit removal instruction) for the package.
If the page names a Maven dependency to add (e.g. mongo-java-driver:3.12.7 bundled
in the content package), record it as manual-action-required — dependency additions
are not applied without user consent.
Before applying a Java-import rewrite, confirm the successor is resolvable in the target module:
$MVN_CMD -pl <module> -am -q dependency:build-classpath \
-Dmdep.outputFile=/tmp/cp.txt > /tmp/mvn-modernize.log 2>&1
grep -l "<successor-path>" /tmp/cp.txtjava.* / javax.* packages are assumed available (they are on every AEM CS JRE).
If the successor is not on the classpath, either (a) add the correct Maven dep with
user consent, or (b) mark the finding as manual-action-required: successor-not-on-classpath.
.java findings)For each Java finding:
Logger.getLogger( →
LoggerFactory.getLogger( for the log4j → SLF4J case), search the file body and
apply it too.LoggerFactory).skipped: deprecated-import-not-found: <prefix> not present in <file>.Before / after example — log4j → SLF4J (hint: "...Please use org.slf4j instead"):
// Before
import org.apache.log4j.Logger;
public class MyService {
private static final Logger log = Logger.getLogger(MyService.class);
}
// After
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyService {
private static final Logger log = LoggerFactory.getLogger(MyService.class);
}pom.xml findings)The analyser flags a deprecated Maven artifact when a bundle in the project depends on it. Read the hint for the recommended action:
<groupId> / <artifactId> and remove
<version> (the AEM SDK BOM manages the version). Insert any extra dependency the
hint requires.<dependency> block.4.3.0) → update <version> or, if it is a
${property} reference, update the property declaration.<dependency> blocks match) → record skipped: maven-dep-ambiguous.Before / after example — commons-lang v2 → v3 (hint: "Please use org.apache.commons.lang3 instead"):
<!-- Before -->
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<!-- After -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>.cfg / .cfg.json / .config findings)The analyser flags OSGi configs whose PID is on Cloud Manager's unmodifiable list. Delete the entire file:
rm "<file>"Verify: git status. AEM CS ignores these configs entirely; leaving them in place is
harmless but the analyser continues to flag them.
| Reason | When |
|---|---|
deprecated-import-not-found: <prefix> not present in <file> | File modified since discovery |
hint-empty: no successor named | Hint field blank; Experience League also silent |
successor-not-on-classpath: <successor> | Successor package not resolvable; needs a new dep |
maven-dep-ambiguous: multiple blocks match <coords> in <file> | Manual fix required |
osgi-config-pid-mismatch: <pid> not referenced in <file> | File exists but contains a different PID |
manual-only: <reason> | Design decision required (webconsole, deep Guava, reflection, SPA Editor) |
Record every skipped finding — do not silently drop.
git diff --statPrint the list of modified files. Verify sensibility before proceeding.
Substitute $SKIP_TESTS_FLAG as set in Step 2 (empty when baseline tests passed —
tests run; -DskipTests when baseline tests were failing).
$MVN_CMD clean install \
$SKIP_TESTS_FLAG \
-DskipFrontend=true \
-Dassembly.skipAssembly=true \
-Dcheckstyle.skip=true \
-Dvault.skipValidation=true \
-Dsling.install.skip=true \
-Dexec.skip=true \
-Dpackage.skip=true \
-Dosgi.bundle.status.skip=true \
-Djacoco.skip=true \
> /tmp/mvn-modernize.log 2>&1; \
MVN_EXIT=$?; \
echo "=== Maven exit code: $MVN_EXIT ==="; \
tail -150 /tmp/mvn-modernize.log; \
exit $MVN_EXITIf exit code = 0: Proceed to Step 7 (skip Step 6).
If exit code != 0: Proceed to Step 6 — the AI-fix loop handles compilation errors from the edits applied in Step 4.
Fix compilation errors that Step 4 edits could not handle. Read /tmp/mvn-modernize.log
to find specific errors.
What you can fix: compilation errors from import rewrites (renamed class, different
method signature), missing imports from package relocations (e.g. commons-lang v2 → v3
StringEscapeUtils moved to commons-text), deprecated API usage that no longer
compiles after a dep version bump, missing dependencies that need to be added as a
result of migration.
What you cannot modify: README / docs, .gitignore, Dockerfiles, build / deploy
scripts, test data files, business logic in working code (only fix compilation errors —
never refactor), .content.xml dialogs, ui.content/ packages, Dispatcher configs.
javax.servlet vs jakarta.servlet — never migrate: AEM as a Cloud Service uses the
javax.servlet namespace (and related APIs: javax.servlet.*, javax.annotation.*).
Never replace these with jakarta.* — it will break the build. Fix javax.servlet
import errors by ensuring the correct AEM SDK dependency is present, not by changing
the namespace.
Every AI-applied code change must be marked with a comment immediately above:
// Fixed by AEM Modernizer AI// Fixed by AEM Modernizer AI above every changed line.org.apache.felix.webconsole.* — webconsole is unavailable on Cloud Service; requires
rethinking the debug/admin approach.com.mongodb.* — the plugin flags the import; making the driver available at
runtime requires embedding org.mongodb:mongo-java-driver:<version> as a bundle
(project-specific Maven configuration that cannot be auto-applied).Ordering, Multimap, or immutable collections beyond
Lists.newArrayList(): replacement requires design decisions.Record each in the report under Manual Action Required with the exact files and line numbers.
Build the project one last time and re-run the analyser — the true test of whether the
migration cleared every past-due deprecation. The pom was never modified, so the
analyser is re-invoked the same way detect.sh did: by its fully-qualified
com.adobe.aem:aemanalyser-maven-plugin:<version>:project-analyse coordinates. Use the
plugin_version and sdk_version from the Step 3 detect.sh meta so this re-run
matches the preflight exactly (latest SDK forced via -DsdkVersion / -DsdkUseDependency=false).
Substitute $SKIP_TESTS_FLAG as set in Step 2 (empty when baseline tests passed —
tests run; -DskipTests when baseline tests were failing).
$MVN_CMD clean verify \
"com.adobe.aem:aemanalyser-maven-plugin:<plugin_version>:project-analyse" \
-DsdkVersion=<sdk_version> -DsdkUseDependency=false \
-Dmaven.clean.failOnError=false \
$SKIP_TESTS_FLAG \
-DskipFrontend=true \
-Dassembly.skipAssembly=true \
-Dcheckstyle.skip=true \
-Dvault.skipValidation=true \
-Dsling.install.skip=true \
-Dexec.skip=true \
-Dpackage.skip=true \
-Dosgi.bundle.status.skip=true \
-Djacoco.skip=true \
> /tmp/mvn-modernize.log 2>&1; \
MVN_EXIT=$?; \
echo "=== Maven exit code: $MVN_EXIT ==="; \
tail -150 /tmp/mvn-modernize.log; \
exit $MVN_EXITReplace <plugin_version> / <sdk_version> with the values from the detect.sh meta.
If the preflight ran with --respect-pom-sdk, drop the -DsdkVersion / -DsdkUseDependency
flags so the pom's own SDK selection is honoured here too.
If exit code = 0 AND log contains zero Usage of deprecated ... found lines:
migration complete. Proceed to Step 8.
If exit code = 0 but analyser still reports deprecations: re-run Step 4 for the new list — some findings were manual-action-only or the AI-fix loop skipped them. Repeat once at most.
If exit code != 0: attempt fixes (same constraints as Step 6). Retry up to 3 times. If still failing, report "Final build failed. Migration is incomplete." Still proceed to Step 8 to capture partial progress.
| Analyser finding | What it means | Action |
|---|---|---|
Usage of deprecated package found : <pkg> | region-deprecated-api — the primary trigger for this skill | Handled by the recipe |
Import-Package not satisfied | Bundle imports a package no other bundle exports | Successor is not on the CS runtime — pick a JDK/available alternative |
api-regions-check violation | Bundle uses an internal/private AEM API | Replace with public AEM API equivalent |
content-package-validation failure | Malformed XML or invalid node type | Fix the flagged content file |
bundle-unversioned-packages | Missing version range on import | Add version constraint in bnd.bnd or Import-Package |
compile-error-unresolved and roll back that file.// Fixed by AEM Modernizer AI immediately above each
changed line in Step 6. Step 4 edits are direct replacements attributable via
git diff.MANDATORY. Always produce a structured report.
git diff --statConfirm: Java source files have import/API upgrades; pom.xml files have dependency
changes; OSGi config files were removed where appropriate; no unexpected files were
modified.
## AEM Modernize Report
**Scan root:** <path>
**Date:** <today>
**Analyser plugin version:** <plugin_version from detect.sh meta>
**Final build status:** <SUCCESS | FAILED>
**Analyser final status:** <CLEAN | RESIDUAL FINDINGS>
### Summary
> Detection driven by aemanalyser-maven-plugin — the deprecation set is refreshed on
> every run. Future-dated deprecations are not flagged by the plugin and are picked
> up automatically on subsequent runs after each removal date passes.
| Category | Found | Fixed via hint | Fixed via Experience League | Manual required |
|---|---|---|---|---|
| Java imports | N | N | N | N |
| Maven `pom.xml` deps | N | N | N | N |
| OSGi unmodifiable PIDs | N | N | N | N |
### Steps Completed
- [x] Step 1: Pre-flight — PASSED
- [x] Step 2: Initial build — PASSED (tests ran: YES → tests mandatory later / NO, baseline tests failing → tests skipped later)
- [x] Step 3: Plugin-driven detection — N findings, plugin v<version>
- [x] Step 4: Edits applied — N files modified
- [x] Step 5: Post-transformation build — PASSED/FAILED
- [x] Step 6: AI-assisted fixes — N fixes applied
- [x] Step 7: Final build + analyser rerun — PASSED/FAILED, analyser CLEAN/RESIDUAL
- [x] Step 8: Report generated
### Files Modified
<list from git diff --stat>
### Fixes Applied
| File | Line | Deprecated package | Successor | Source |
|---|---|---|---|---|
| core/src/Foo.java | 5 | org.apache.log4j | org.slf4j | hint |
| core/pom.xml | 12 | commons-lang:commons-lang | org.apache.commons:commons-lang3 | hint |
| core/src/Bar.java | 8 | com.day.cq.xss | org.apache.sling.xss | Experience League |
### Manual Action Required
For each item:
- What was found (file:line, deprecated package)
- Why it couldn't be auto-fixed (design decision required / no successor named by
hint or Experience League / manual API redesign)
- What Adobe Experience League says (quote or link)
### Analyser final output
- If CLEAN → "No past-due deprecated APIs detected against
aemanalyser-maven-plugin v<version>."
- If RESIDUAL → list each remaining `Usage of deprecated ... found` line and its
disposition (deferred, manual-action, third-party).
### Errors
<list of errors encountered, or "None">
### Warnings
<list of warnings from the analyser or elsewhere, or "None">
### Next Steps
1. Trigger a Cloud Manager code quality pipeline to confirm compliance.
2. Re-run this skill after future removal dates pass — the plugin picks up newly
past-due deprecations automatically.
3. Run tests: `mvn test` to verify no behavioral regressions..tessl-plugin
skills
aem-rde
aem-workflow
code-assessment
content-distribution
create-component
assets
references
dispatcher
incident-response
references
dispatcher-foundation
performance-tuning
references
dispatcher-foundation
security-hardening
references
dispatcher-foundation
technical-advisory
references
dispatcher-foundation
workflow-orchestrator
references
dispatcher-foundation
ensure-agents-md
references
migration
references
dispatcher
legacy-ui
template-modernization
validation
scripts
dispatcher-tool
validate-migration