For AI agents: the complete documentation index is available at https://nhanbe.id.vn/llms.txt, the full documentation bundle is available at https://nhanbe.id.vn/llms-full.txt, and this page is available as Markdown at https://nhanbe.id.vn/aem/advanced/maven-packages-and-project-structure.md.

Maven, Packages and the AEM Project Structure

25 – Maven, Packages and the AEM Project Structure

Target: 3–4 years experienced AEM Developer Covers from your additional list: Maven · Package Manager · Deployment process · project structure Project domain: a global energy technology company's marketing site.


Before we start — the question underneath this topic

This topic has been referenced from almost every earlier file without ever being explained. ui.apps, ui.content, ui.config, all, filter.xml, mode="merge" — they've appeared in files 01, 03, 04, 06, 13 and 14 as things you should know, and this is where they get explained.

But the interview question is rarely "list the Maven modules." It's:

"How does your code actually get from your laptop onto a production AEM instance?"

That's a question about the whole chain — modules, packages, filters, build, deploy. And the part that separates a good answer is understanding why the modules are split the way they are, because it isn't arbitrary. It follows directly from the immutable/mutable distinction in file 01:

/apps is code. /content and /conf are data. They have different lifecycles, so they ship in different packages.

Get that, and the module structure stops being a list to memorise and becomes something you can derive.


1. Introduction

1.1 What a package actually is

Before Maven, the simpler concept:

An AEM package is a ZIP file containing repository content, plus a filter.xml that declares exactly which paths are included.

That's it. It's built by a tool called FileVault (often abbreviated vlt), which serialises repository nodes into files on disk and back again.

Two things follow from that definition, and both matter:

The filter.xml is what defines the package, not the folder structure. You can have files sitting in your project that aren't in any filter, and they simply won't be deployed. That surprises people — "it's in my project, why isn't it on the server?"

Installing a package writes to the repository. It's a content operation. Which means it can overwrite things, and by default it does — section 2.4.

1.2 Why the project is split into modules

Here's the reasoning, and it's the answer to "why so many modules?"

Your project contains fundamentally different kinds of thing:

Kind of thingExampleLifecycle
Java codeA Sling Model, an OSGi serviceCompiled, becomes a bundle
Immutable contentComponents, clientlibs, template typesDeployed, never changed at runtime
Mutable contentPages, editable templates, policiesChanged by authors at runtime
ConfigurationOSGi configsDeployed, per run mode
Front-end sourceSCSS, TypeScriptCompiled by webpack
Dispatcher configFilter and cache rulesDeployed to the web tier

Those have genuinely different lifecycles, and mixing them causes real problems. The worst one: if pages and components ship in the same package, redeploying your components would overwrite author content.

So the modules exist to keep them apart. Once you see it that way, the structure is obvious rather than arbitrary.

1.3 A real project example to adapt

"We use the standard AEM archetype structure — core for the Java bundle, ui.apps for components and clientlibs, ui.content for our templates and sample content, ui.config for OSGi configs, ui.frontend as a webpack build, dispatcher for the web tier, and all as the aggregate that Cloud Manager actually deploys.

The split that matters most is ui.apps versus ui.content. ui.apps is immutable — on Cloud Service it's baked into the container image and read-only at runtime. ui.content is mutable content, and every one of its filters uses mode="merge" so a deployment doesn't wipe the templates and policies our content leads have adjusted.

Day to day I mostly deploy just the module I've changed — -pl core for a Java change is much faster than rebuilding everything — and only build the full all package when I need to verify the whole thing."

That covers the structure, the reason for the key split, and the practical workflow — three follow-ups pre-empted.


2. Core Concepts

2.1 The modules, and what each is for

The AEM Project Archetype generates this structure. Learn what each module is for rather than just its name.

my-project/
├── core/            Java code → an OSGi bundle
├── ui.apps/         Components, clientlibs, template types  (IMMUTABLE)
├── ui.content/      Pages, templates, policies              (MUTABLE)
├── ui.config/       OSGi configurations
├── ui.frontend/     SCSS / TypeScript, built by webpack
├── ui.tests/        UI / end-to-end tests
├── it.tests/        Integration tests
├── dispatcher/      Dispatcher configuration
├── all/             The AGGREGATE package that gets deployed
└── pom.xml          The parent POM

Going through them properly:

core — all your Java. Sling Models, OSGi services, servlets, filters, workflow process steps, job consumers. It compiles into an OSGi bundle, which is a JAR with the manifest headers from file 06.

ui.apps — everything under /apps. Components, their dialogs and HTL, clientlibs, template types. This is immutable content — on Cloud Service it's baked into the container image and read-only at runtime (file 14).

ui.content — everything under /content and /conf. Editable templates, policies, sample or initial content, Content Fragment Models. This is mutable content, which authors change at runtime.

ui.config — OSGi configurations, organised into run-mode folders (file 06). Technically these live under /apps, but the archetype separates them because configuration changes for different reasons than code does.

ui.frontend — an npm and webpack project (file 04). The front-end team works here with SCSS and TypeScript, and the build output is copied into a clientlib in ui.apps.

dispatcher — the dispatcher configuration (file 19). On Cloud Service this is deployed by the web-tier pipeline, which is much faster than a full-stack one.

it.tests and ui.tests — integration and UI tests, run by the Cloud Manager pipeline (file 14).

all — and this one needs explaining, because its purpose isn't obvious.

2.2 Why the all package exists

The all module contains no content of its own. It's an aggregate — a package whose only job is to embed the other packages.

Why that's necessary: Cloud Manager deploys exactly one package. It doesn't take a list. So something has to bundle core, ui.apps, ui.content and ui.config into a single deployable artefact, and that's all.

And it does one more thing that matters: it declares the order things install in, and it separates the mutable and immutable parts so the Cloud Service build validation can check them.

The interview answer:

"The all package is an aggregate — it contains nothing itself, it just embeds the other packages. It exists because Cloud Manager deploys exactly one package rather than taking a list, so something has to bundle the bundle, the apps content, the mutable content and the configs into one artefact. It also declares the install order, and keeps the mutable and immutable parts distinguishable so the Cloud Service build validation can check that we haven't mixed them."

2.3 filter.xml — what actually defines a package

This is the most important file in the packaging story, and the most commonly misunderstood.

<?xml version="1.0" encoding="UTF-8"?>
<workspaceFilter version="1.0">
    <filter root="/apps/energy"/>
    <filter root="/apps/energy-vendor-packages"/>
</workspaceFilter>

The filter declares which repository paths this package covers. Not which files are in your project folder — which paths the package claims.

Two consequences that catch people out:

A file in your project that isn't under a filter root won't be deployed. It builds fine, it's in the JAR, and it never reaches the repository. "It works locally but not on the server" is often exactly this — locally you'd copied it manually at some point.

A filter root claims that entire path — which brings us to the next section, and the single most dangerous property in AEM packaging.

2.4 mode="merge" — the property that stops you destroying content

By default, installing a package REPLACES everything under its filter root.

Read that again, because the consequence is severe. If ui.content has:

<filter root="/content/energy"/>

then every deployment deletes everything under /content/energy and replaces it with whatever is in the package. Every page an author has created since the last release, gone.

mode="merge" changes that:

<filter root="/content/energy" mode="merge"/>

With merge, existing content is left alone and only nodes present in the package are added or updated.

The filter modes:

ModeBehaviour
replace (default)Delete everything under the root, then install the package content
mergeAdd and update; leave existing nodes alone
updateUpdate existing nodes, don't remove

The rule to state:

Anything under /content or /conf must use mode="merge". Those are paths authors change. Anything under /apps can use the default replace, because that's your code and a clean replace is what you want.

This is a genuinely good thing to raise unprompted, because getting it wrong destroys production content and it's a mistake people only make once.

2.5 Filter rules — include and exclude

Filters can be more precise than a root path:

<filter root="/apps/energy">
    <exclude pattern="/apps/energy/install(/.*)?"/>
</filter>

<filter root="/content/dam/energy" mode="merge">
    <include pattern="/content/dam/energy/logos(/.*)?"/>
</filter>

exclude removes paths from a filter that would otherwise be covered. include narrows a filter to only matching paths.

A practical use: excluding an install folder so a nested package isn't accidentally shipped, or including only a specific asset folder rather than the whole DAM.

2.6 The Maven build

Maven is the build tool. The parent pom.xml declares the modules and the shared configuration; each module has its own POM.

The commands you'll actually use — and knowing the targeted ones is a small signal that you develop day to day rather than only building everything:

# Build and deploy EVERYTHING to a local author on 4502
mvn clean install -PautoInstallSinglePackage

# Same, but to a local publish on 4503
mvn clean install -PautoInstallSinglePackagePublish

# Deploy ONLY the Java bundle — much faster while iterating on backend code
mvn clean install -PautoInstallBundle -pl core

# Deploy ONLY ui.apps — faster while iterating on components and HTL
mvn clean install -PautoInstallPackage -pl ui.apps

# Just build, don't deploy
mvn clean install

The -pl flag means "project list" — build only that module. That's the difference between a fifteen-second deploy and a two-minute one, and during active development it matters a lot.

The profiles (-P) are defined in the POM and control what happens after the build — autoInstallSinglePackage uploads and installs the all package; autoInstallBundle uploads just the bundle.

2.7 Dependency scopes — the one that breaks bundles

This is the Maven detail most likely to come up, because getting it wrong produces a confusing OSGi failure.

AEM's APIs come from a dependency — the uber-jar on 6.5, or the aem-sdk-api on Cloud Service. And the scope you give it matters:

<dependency>
    <groupId>com.adobe.aem</groupId>
    <artifactId>aem-sdk-api</artifactId>
    <version>${aem.sdk.api}</version>
    <scope>provided</scope>   <!-- ← THIS -->
</dependency>

provided means "this will be available at runtime; don't package it."

If you use compile instead — the default — Maven embeds a copy of the AEM API into your bundle. Now there are two copies of the same packages in the OSGi container at potentially different versions, and your bundle's imports can't be satisfied.

The symptom is the file 06 problem: the bundle sits in INSTALLED state, never activates, and your code silently never runs.

So the chain to be able to state: wrong Maven scope → duplicate packages → unsatisfied Import-Package → bundle stuck in INSTALLED → your code never runs, with no error. That's a genuinely good answer because it connects a build-tool detail to a runtime symptom.

The scopes worth knowing:

ScopeMeaningUse for
providedAvailable at runtime, not packagedAEM APIs, OSGi APIs, Servlet API
compile (default)Packaged into your bundleThird-party libraries AEM doesn't have
testOnly on the test classpathJUnit, Mockito, AEM Mocks

2.8 Embedding third-party libraries

If your code needs a library AEM doesn't provide, you have to get it into the OSGi container.

Two approaches:

Embed it in your bundle — the library is packaged inside your JAR and its packages are private to you. Simple, and the right answer for a small utility library.

Deploy it as its own bundle — if the library is already an OSGi bundle, or several of your bundles need it. Cleaner, but more moving parts.

The thing to watch: embedding a library that AEM already provides at a different version causes exactly the duplicate-package problem from 2.7. Before embedding anything, it's worth checking whether AEM already exports it — /system/console/depfinder tells you which bundle exports a given package (file 06).

2.9 Package Manager

Package Manager at /crx/packmgr is the UI for uploading, installing, building and downloading packages.

What it's genuinely useful for:

Building a content package to move content between environments — export from one, install on another.

Inspecting a package — what filters does it have, what's actually inside it.

Downloading what's currently installed.

And the important caveat: you can't use it to deploy on Cloud Service production. Deployment is Cloud Manager pipelines only (file 14). Package Manager still exists on development environments, but the deployment path is closed deliberately — that's the discipline benefit from file 14, and it means production can't drift from Git.

On 6.5 you can deploy via Package Manager, and plenty of teams did. It's also exactly how production drifts from source control, which is the argument against it.

2.10 Content packages for moving content

A practical use worth knowing, and it comes up as a scenario question.

Moving content from production to a lower environment for debugging:

  1. Build a package on production with a filter covering just the paths you need
  2. Download it
  3. Install it on the lower environment

Three things to be careful about:

Filter precisely. A filter of /content on a large site produces an enormous package.

Exclude what you shouldn't copy — /home (users and their data), anything containing credentials, and personal data. This is a genuine data-protection concern, not just tidiness.

Watch for mode="replace" — installing a content package on the target will replace what's there unless you set merge.

On Cloud Service there's a Content Copy feature in Cloud Manager for exactly this, which is safer than hand-built packages.

2.11 The mutable/immutable split on Cloud Service

This is where the module structure becomes mandatory rather than good practice (file 14).

On Cloud Service, the build validates that packages don't mix mutable and immutable content. If ui.apps contains anything under /content, or ui.content contains anything under /apps, the build fails.

Which is why the split exists as a hard rule:

PackageContainsPaths
ui.appsImmutable — code/apps
ui.contentMutable — data/content, /conf
ui.configOSGi configs/apps/<project>/osgiconfig

A common migration finding is a project where components and sample content shipped together in one package, which worked fine on 6.5 and fails validation on Cloud Service. That's what the Repository Modernizer tool restructures (file 14).


3. Internal Working

3.1 From your laptop to the repository

graph TD A["mvn clean install"] --> B["core → compiled into an OSGi BUNDLE"] A --> C["ui.frontend → webpack build"] C --> D["output copied into a clientlib in ui.apps"] A --> E["ui.apps → FileVault ZIP (immutable)"] A --> F["ui.content → FileVault ZIP (mutable)"] A --> G["ui.config → FileVault ZIP"] B --> H["ALL package embeds them"] E --> H F --> H G --> H H --> I{"Local, or Cloud Manager?"} I -->|Local| J["Uploaded to Package Manager<br/>and installed"] I -->|Cloud| K["Pipeline: quality gates →<br/>images → stage → production"]

Two things worth drawing out:

ui.frontend builds first, and its output lands in ui.apps before that package is assembled. That's why editing the generated css.txt by hand gets overwritten (file 04) — the build regenerates it every time.

The bundle is embedded in the package. Your Java doesn't deploy separately; it's inside ui.apps (or all) in an install folder, and the Sling Installer picks it up.

3.2 How a package installs

graph TD A["Package uploaded"] --> B["Read filter.xml"] B --> C{"For each filter root:<br/>what mode?"} C -->|"replace (default)"| D["DELETE everything under the root"] C -->|"merge"| E["Leave existing nodes alone"] D --> F["Write the package content"] E --> F F --> G{"Any bundles in an<br/>install folder?"} G -->|Yes| H["Sling Installer picks them up<br/>→ OSGi bundle lifecycle (file 06)"] G -->|No| I["Done"]

The left branch is the dangerous one, and it's why mode="merge" matters so much on content paths. A default-mode filter on /content/energy deletes every page authors created since the last release, and the package installs successfully — no error, because it did exactly what it was told.

3.3 The Sling Installer

Worth knowing because it explains how a bundle inside a content package becomes a running bundle.

The Sling Installer watches /apps/**/install and /libs/**/install. When a JAR or a .cfg.json appears there, it picks it up — installing bundles and applying configurations.

Which is why: your compiled bundle ends up in an install folder inside the package, and OSGi configurations end up in config.<runmode> folders. The installer handles both, and everything from file 06's bundle lifecycle takes over from there.


8. Configuration Examples

8.1 ui.apps filter — immutable content

ui.apps/src/main/content/META-INF/vault/filter.xml

<?xml version="1.0" encoding="UTF-8"?>
<workspaceFilter version="1.0">

    <!-- IMMUTABLE content: our code.
         No mode attribute, so the default 'replace' applies -- and
         that is exactly what we want here. A clean replace means the
         deployed state matches the build exactly, with no leftovers
         from a previous release.

         NOTE: nothing under /content or /conf may appear in this
         package. On Cloud Service the build VALIDATES that and fails
         if mutable and immutable content are mixed. -->
    <filter root="/apps/energy"/>

    <!-- Third-party packages embedded in the build -->
    <filter root="/apps/energy-vendor-packages"/>

    <!-- Oak index definitions are immutable -->
    <filter root="/oak:index/energy-products-custom-1"/>

</workspaceFilter>

8.2 ui.content filter — mutable content

ui.content/src/main/content/META-INF/vault/filter.xml

<?xml version="1.0" encoding="UTF-8"?>
<workspaceFilter version="1.0">

    <!-- MUTABLE content: things AUTHORS change.
         mode="merge" IS NOT OPTIONAL HERE.
         Without it, every deployment DELETES everything under this
         root and replaces it with what is in the package -- which
         means every page authored since the last release.
         The package installs SUCCESSFULLY while doing it, so there
         is no error and nothing in any log. -->
    <filter root="/content/energy" mode="merge"/>

    <!-- Editable templates, policies, Content Fragment Models.
         Template authors adjust these at runtime (file 03), so the
         same rule applies. -->
    <filter root="/conf/energy" mode="merge"/>

    <!-- Only the brand asset folder, not the whole DAM -->
    <filter root="/content/dam/energy" mode="merge">
        <include pattern="/content/dam/energy/brand(/.*)?"/>
    </filter>

    <!-- Experience Fragments are pages under their own root (file 16) -->
    <filter root="/content/experience-fragments/energy" mode="merge"/>

</workspaceFilter>

The mode="merge" comment is the thing to be able to explain, and it's story 1.

8.3 The AEM API dependency

core/pom.xml

<dependencies>

    <!-- The AEM API.
         SCOPE MUST BE 'provided'. It is available at runtime and must
         NOT be packaged into our bundle.

         With the default 'compile' scope, Maven embeds a copy of the
         AEM API into our JAR. There are then two copies of the same
         packages in the OSGi container at potentially different
         versions, our Import-Package cannot be satisfied, and the
         bundle sits in INSTALLED state and never activates -- with
         no exception anywhere (file 06). -->
    <dependency>
        <groupId>com.adobe.aem</groupId>
        <artifactId>aem-sdk-api</artifactId>
        <scope>provided</scope>
    </dependency>

    <!-- Test-only: never packaged -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>io.wcm</groupId>
        <artifactId>io.wcm.testing.aem-mock.junit5</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-core</artifactId>
        <scope>test</scope>
    </dependency>

</dependencies>

8.4 The commands worth memorising

# ---------- FULL BUILD AND DEPLOY ----------

# Everything → local AUTHOR on 4502
mvn clean install -PautoInstallSinglePackage

# Everything → local PUBLISH on 4503
mvn clean install -PautoInstallSinglePackagePublish

# ---------- TARGETED (what you actually use all day) ----------

# Only the Java bundle. Seconds rather than minutes.
mvn clean install -PautoInstallBundle -pl core

# Only ui.apps -- components, HTL, clientlibs
mvn clean install -PautoInstallPackage -pl ui.apps

# Only ui.content -- templates, policies
mvn clean install -PautoInstallPackage -pl ui.content

# ---------- BUILD WITHOUT DEPLOYING ----------
mvn clean install

# Skip tests (for a quick local check only -- never in CI)
mvn clean install -DskipTests

# ---------- CREATE A NEW PROJECT ----------
mvn -B org.apache.maven.plugins:maven-archetype-plugin:3.2.1:generate \
  -D archetypeGroupId=com.adobe.aem \
  -D archetypeArtifactId=aem-project-archetype \
  -D archetypeVersion=48 \
  -D appTitle="Energy Sites" \
  -D appId="energy" \
  -D groupId="com.energy" \
  -D aemVersion="cloud"

-pl is the flag worth knowing — it means "project list", building only that module. During active development that's the difference between a fifteen-second cycle and a two-minute one, and knowing it is a small signal that you develop day to day.

8.5 A content package filter for moving content between environments

<?xml version="1.0" encoding="UTF-8"?>
<workspaceFilter version="1.0">

    <!-- Precise. A filter of /content on a large site produces an
         enormous package that may not even install. -->
    <filter root="/content/energy/de/de/products" mode="merge"/>

    <!-- Only the assets these pages actually reference -->
    <filter root="/content/dam/energy/products/transformers" mode="merge"/>

    <!-- Content Fragments are ASSETS in the DAM (file 15), so they
         need their own filter -- they are NOT included by publishing
         or packaging the pages that reference them. -->
    <filter root="/content/dam/energy/fragments/products" mode="merge"/>

    <!-- DELIBERATELY NOT INCLUDED:
           /home            users, groups, and personal data
           /conf            unless the config genuinely needs moving
           anything with credentials

         This is a data-protection concern, not just tidiness. -->

</workspaceFilter>

The Content Fragment filter is the detail worth pointing at — it's the same trap as file 15 and file 18. Fragments are separate assets, so packaging the pages that reference them doesn't include them, exactly as publishing the pages doesn't publish them.


9. Common Mistakes

The mistakeWhat actually happensThe fix
No mode="merge" on a content filterDeployment silently DELETES author content and reports successmode="merge" on every /content and /conf filter
compile scope on an AEM APIDuplicate packages → bundle stuck in INSTALLED, code silently never runsprovided
A file not under any filter rootBuilds fine, never deploys — "works locally"Check the filter when adding a path
Mixing /apps and /content in one packageCloud Service build validation failsSplit into ui.apps and ui.content
Embedding a library AEM already providesSame duplicate-package problemCheck /system/console/depfinder first
A /content filter that's too broadAn enormous package that may not installFilter precisely
Including /home in a content packageCopies users and personal data between environmentsExclude it, always
Only ever deploying one moduleLocal state diverges from a clean installFull build before pushing
Editing generated css.txt / js.txtOverwritten by the next ui.frontend buildChange the webpack config (file 04)
Deploying via Package Manager on 6.5 productionProduction drifts from GitPipeline, even where it's technically possible
Assuming packaging pages includes their fragmentsFragments are separate assetsTheir own filter
Forgetting Oak index definitions in the filterIndexes never deploy; queries traverseInclude the index path
-DskipTests in CITests stop protecting youLocal quick checks only

10. Best Practices

On filters. mode="merge" on every /content and /conf filter, without exception, checked in review rather than assumed. Filter precisely. Never include /home.

On modules. Keep the mutable/immutable split clean — it's enforced on Cloud Service anyway, and it's right on 6.5 too. Don't split ui.apps per brand; share the code and differentiate through policies and configuration.

On dependencies. provided for AEM APIs, test for test libraries, compile only for third-party libraries AEM genuinely doesn't have. Check depfinder before embedding anything.

On the build. Deploy targeted while iterating, full build before pushing. Never skip tests in CI.

On deployment. Pipeline only, even where a manual route technically exists. Anything created by hand doesn't exist on the next environment — that's the file 13 and 14 lesson, and packaging is where it bites.


11. Debugging Tips

"It works locally but not on the server." This is the signature packaging problem, and the check is: inspect the built package, not the project folder. Download the artefact from Package Manager, open it, and see whether the file is actually there. The project folder tells you what you wrote; the package tells you what deploys.

"My bundle isn't active." /system/console/bundles — INSTALLED means an unsatisfied import, which very often traces back to a Maven scope. /system/console/depfinder tells you which bundle exports a given package.

"Content disappeared after a deployment." A filter without mode="merge". Check the filter file for the affected path, and check whether the deployment reported success — it will have.

"The configuration isn't applying." Check the run-mode folder name and the file name against the component PID (file 06), and confirm the file is actually under a filter root.

ToolAnswers
/crx/packmgrWhat's installed; download and inspect a package
Package Manager → the package's filtersWhat paths does it actually claim
/system/console/bundlesIs the bundle ACTIVE, and which import is unsatisfied
/system/console/depfinderWhich bundle exports this package
/system/console/configMgrWhat configuration values are actually in effect
The built artefact in all/target/What the build actually produced

12. Performance Notes

Build time is developer time. Targeted deploys with -pl are the single biggest saving during active development.

Package size matters for install time. A content package covering a large subtree takes real time to install and can time out. Filter precisely.

A large content package on production is an operation, not a background task — it writes a lot of nodes and competes with authoring.

The Cloud Manager pipeline duration is dominated by the quality gates and stage tests, not the build. Which is why the faster pipeline types exist (file 14) — a dispatcher rule change doesn't need the full cycle.


15. Comparison Tables

The modules

ModuleContainsMutable?Notes
coreJava—Compiles to an OSGi bundle
ui.apps/apps — components, clientlibsImmutableRead-only at runtime on cloud
ui.content/content, /confMutableNeeds mode="merge"
ui.configOSGi configs—Run-mode folders
ui.frontendSCSS, TS—Webpack; output → ui.apps
dispatcherWeb tier config—Web-tier pipeline
it.tests / ui.testsTests—Run by the pipeline
allNothing—Aggregate — embeds the rest

Filter modes

ModeBehaviourUse for
replace (default)Deletes everything under the root first/apps — your code
mergeAdds and updates; leaves existing alone/content, /conf
updateUpdates existing, doesn't removeRare

Maven scopes

ScopePackaged?Use for
providedNoAEM APIs, OSGi APIs
compile (default)YesLibraries AEM doesn't have
testNoJUnit, Mockito, AEM Mocks

Deployment routes

Route6.5Cloud Service
Maven profile (local)YesYes (local SDK)
Package ManagerYes — and that's how drift happensDev environments only
Cloud Manager pipeline—The only route to production
RDE—Seconds, dev iteration (file 14)

Next topic

26-Cloud-Manager-Pipelines-and-CI-CD — Cloud Manager in depth, the pipeline types and quality gates, Git strategy and branching, environments, and what "CI/CD" actually looks like on an AEM project.


Topic 25 of the AEM Interview Preparation repository. Teaching style, energy-sector project domain.