AEM Project Structure Explained
Series: AEM Foundations — Part 5
When you generated a project from Adobe's Maven archetype earlier in this series, it created several modules with cryptic-looking names — core, ui.apps, ui.content, ui.config, and more. This post breaks down what each module is for and why AEM projects are structured this way.
Why AEM Projects Are Multi-Module
Unlike a typical web app where everything might live in one deployable unit, AEM separates code, content, and configuration into distinct Maven modules. This separation exists because these three things have very different lifecycles:
Code changes with every feature or bug fix
Content (sample pages, component defaults) is set up once and rarely redeployed
Configuration (OSGi settings) often differs between environments (dev, stage, prod)
Keeping them separate means you can deploy code changes without accidentally overwriting content, and manage environment-specific configuration independently.
The Core Modules
core
This is where your Java code lives — Sling Models, Servlets, OSGi services, schedulers, and any custom business logic. If you're writing Java for AEM, this is almost always where it goes. It compiles into an OSGi bundle that gets deployed into AEM's runtime.
ui.apps
This holds everything that goes under /apps in the JCR repository:
Component definitions (dialogs, HTL scripts,
.content.xmlfiles)Client libraries (CSS/JS, bundled as
clientlibs)Templates and template policies
Any other application-level structure
Recall from the previous post in this series: /apps is where you place your customizations, which Sling looks up before falling back to Adobe's defaults under /libs. This module is effectively "your app's definition" in the repository.
ui.content
This contains sample or default content — example pages, folder structures under /content, and sometimes reference content used for demos or initial setup. In many real projects, this module is either minimal or excluded from production deployments entirely, since live content is authored directly in the Author environment rather than deployed via code.
ui.config
This holds OSGi configuration — settings for things like PDF processing, workflow launchers, or any configurable service, often split by run mode (author vs. publish, dev vs. prod) so the same codebase behaves correctly across environments.
ui.frontend (in newer archetype versions)
Newer AEM projects often include a dedicated frontend module using modern tooling (webpack, npm) to build client-side assets, which then get packaged into ui.apps clientlibs during the build. This lets front-end developers work with familiar JS/CSS tooling rather than hand-writing clientlib folder structures.
it.tests (optional)
Some projects include an integration test module here, used for automated tests that run against a deployed AEM instance as part of CI/CD pipelines.
all
This is a "wrapper" package that bundles core, ui.apps, ui.content, and ui.config together into a single deployable content package. When you ran mvn clean install -PautoInstallSinglePackage in the previous post, this is typically the package that actually got installed onto your local instance.
A Simplified Visual
my-project/
├── core/ → Java code (Sling Models, Servlets, OSGi services)
├── ui.apps/ → /apps content: components, clientlibs, templates
├── ui.content/ → /content sample or reference content
├── ui.config/ → OSGi configurations per run mode
├── ui.frontend/ → Modern frontend build tooling (if present)
├── it.tests/ → Integration tests (optional)
└── all/ → Combined deployable package
Why This Structure Pays Off Long-Term
This separation might feel like overhead on a small project, but it becomes essential as a project grows:
Multiple developers can work on
core(Java logic) andui.apps(components/front-end) simultaneously with minimal conflictEnvironment-specific behavior lives cleanly in
ui.config, instead of scatteredif (environment == "prod")checks in codeCI/CD pipelines can build and deploy the
allpackage as a single unit, while still keeping the underlying modules independently testable
Series Wrap-Up
That completes the AEM Foundations series. At this point you should understand:
What AEM actually is, and how Sites, Assets, and Forms differ
The Author/Publish/Dispatcher architecture and how requests flow through it
How to set up a local AEM SDK environment
How Sling resolves resources and processes requests
How a real AEM project is organized on disk
From here, the AEM Core Development series picks up with hands-on component building — starting with your first HTL component and Sling Model.
