How an AEM Maven Project Actually Builds a Deployable Package
How the parent pom.xml and the core, ui.apps, ui.content, ui.config, and all modules of an AEM project combine to produce a single deployable package, and why the all module is the real deployment unit.
An AEM project generated with the AEM Project Archetype doesn’t produce a
single artifact — it produces half a dozen. The usual confusion for someone
new to an AEM codebase isn’t “what does Maven do,” it’s “which of all these
pom.xml files is the one that actually gets deployed to the instance.” The
short answer: almost none of them individually — what gets deployed is the
output of one specific module, all, which assembles the rest.
The multimodule layout the archetype generates
Generating a project with com.adobe.aem:aem-project-archetype gives you a
root parent (aggregator) pom.xml and several child modules, each with its
own pom.xml and its own packaging type. The standard modules are:
core— an OSGi bundle (jar) with services, listeners, schedulers, Sling Models, and servlets: all the project’s Java code.ui.apps— a content-package deployed to/apps: HTL, client libraries, and component definitions.ui.content— a content-package with sample mutable content (pages, site configuration under/contentand/conf).ui.config— a content-package with runmode-specific OSGi configurations and Repo-init scripts.ui.frontend— a frontend build (npm/webpack) whose output (compiled JS/CSS) ends up packaged insideui.apps.all— a “container” content-package with no content of its own: it only embeds the artifacts of the modules above.
The internals of ui.apps and ui.frontend deserve their own dedicated
articles — what matters here is how all the modules fit together to produce
the final package, not how each one works internally.
Two packaging types: bundle vs. content-package
core is the only module with plain jar packaging: it’s an OSGi bundle
like any other, compiled and packaged with the usual bundle plugin. The
remaining content-producing modules (ui.apps, ui.content, ui.config,
all) use content-package packaging, built by the
filevault-package-maven-plugin (org.apache.jackrabbit). This plugin
replaced Adobe/Day’s older content-package-maven-plugin and is currently
the only one supported on AEM as a Cloud Service.
Each content-package module also declares a packageType in the plugin
configuration:
application→ui.apps(immutable code, deployed to/apps).content→ui.contentandui.config(mutable content and configuration).container→all(only embeds other packages, no content or code of its own).
That classification isn’t cosmetic: Cloud Manager’s package validation uses
it to reject builds where, for example, a package marked application
touches /content paths it shouldn’t.
The all module: how the final package gets assembled
The all module’s pom.xml declares zip-type dependencies (or jar for
core) on the other modules, and the filevault-package-maven-plugin
configuration uses <embeddeds> to specify the install path each one gets
embedded at inside the container package. This is the current approach; the
older <subPackages> mechanism is deprecated.
<!-- all/pom.xml (fragment) -->
<plugin>
<groupId>org.apache.jackrabbit</groupId>
<artifactId>filevault-package-maven-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<group>com.myproject</group>
<packageType>container</packageType>
<embeddeds>
<embedded>
<groupId>com.myproject</groupId>
<artifactId>myproject.core</artifactId>
<type>jar</type>
<target>/apps/myproject-packages/application/install</target>
</embedded>
<embedded>
<groupId>com.myproject</groupId>
<artifactId>myproject.ui.apps</artifactId>
<type>zip</type>
<target>/apps/myproject-packages/application/install</target>
</embedded>
<embedded>
<groupId>com.myproject</groupId>
<artifactId>myproject.ui.content</artifactId>
<type>zip</type>
<target>/apps/myproject-packages/content/install</target>
</embedded>
</embeddeds>
</configuration>
</plugin>
Each <embedded> needs a matching <dependency> in the same pom.xml,
pointing at the sibling module’s artifact version (normal in a Maven
reactor, where ${project.version} keeps every module in sync).
Why this matters: a single deployable artifact
Running mvn clean install on the all module produces a single
installable .zip. Instead of manually uploading four or five packages in
the right order to Package Manager, or coordinating multiple steps in a
deployment pipeline, the Cloud Manager pipeline (or any direct deployment
script) uploads and activates one package. That single package is what
actually defines which version of code and content is running on an AEM
instance at any given moment.
This is also what makes the build deterministic: the root aggregator lists
modules under <modules>, but it’s the dependency graph declared in each
pom.xml — not the order of that list — that Maven uses to compute the
reactor’s actual build order.
Where this comes up
- A Cloud Manager build fails because of a missing embed: if you add a
new module or simply forget to add its
<dependency>and<embedded>entry toall/pom.xml, the build can pass cleanly and produce anallpackage with no errors — your new bundle or content just never gets deployed, because it was never embedded. - Changing
ui.contentdoesn’t force a rebuild ofcore: sincecoreandui.contenthave no compile-time dependency on each other (onlyalldepends on both), you can iterate on sample content without touching Java, and in CI you can scope the build with-pl ui.content -aminstead of rebuilding the bundle on every change. - “My component isn’t showing up after deployment”: before suspecting
Sling or dispatcher caching, check whether the module that contains it is
actually declared as a dependency and as an
embeddedinall’spom.xml. It’s the most common cause of “it compiled fine but isn’t there.”