Welcome to dotCMS/core. This guide gets you from a fresh clone to your first
merged PR. It complements (does not replace) CLAUDE.md, which is the
canonical reference for build commands, project structure, and coding patterns.
How to use this with Claude Code: open the repo and ask Claude things like "help me set up my environment", "run just the FooTest integration test", or "walk me through opening my first PR". The repo-specific skills listed below are aware of dotCMS conventions.
See also:
dotFrontendOnboarding.mdanddotBackendOnboarding.mdfor deeper, discipline-specific onboarding.
sdk env install # Java 25 via SDKMAN, pinned in .sdkmanrc
nvm use # Node 22.15+ via nvm, pinned in .nvmrc- Java: core modules compile to and run on Java 25. A wrong JDK gives cryptic
compile errors — check
java -versionfirst when a build misbehaves. - Node: the
core-web/frontend build fails on the wrong Node version. Alwaysnvm usefrom the repo root before touching frontend code. - Docker: required for
just dev-runand Docker-backed tests. dotCMS runs in Docker by default, so you do not need a local PostgreSQL or Elasticsearch install — they come up in containers. just: the command runner used throughout this guide.brew install just
- Backend (Java): IntelliJ IDEA — open the root
pom.xmlto import. - Frontend (Angular/TS): VS Code or Cursor, optionally with the Nx Console extension.
just install-all-mac-deps # installs/validates Git, JDK, Docker, etc. (macOS)Then build (pick by scope):
# Core + in-project deps — the everyday build (~2-3 min) ✅
./mvnw install -pl :dotcms-core --am -DskipTests
# Equivalent via just
just build-select-module dotcms-core
# Full rebuild, skip Docker image (~8-15 min)
just build-no-docker # = ./mvnw clean install -DskipTests -Ddocker.skip
./mvnw install -pl :dotcms-core -DskipTests(without--am) can fail with missing in-project deps — prefer the--amform orjust build-select-module.
# Backend in Docker (PostgreSQL + Elasticsearch + dotCMS)
just dev-start-on-port 8080 # default port is 8082 if omitted; `just dev-start` picks a random port
just dev-stop # stop it
just dev-run # run with Glowroot profiler
# Frontend dev server (use `yarn nx`, never bare `nx`)
cd core-web && yarn nx serve dotcms-ui
# served at http://localhost:4200/dotAdmin| Symptom | Likely cause | Fix |
|---|---|---|
| Weird compile errors | Wrong JDK | sdk env install, confirm java -version is 25 |
| Frontend build fails immediately | Wrong Node | nvm use from repo root |
nx: command not found / odd nx behavior |
Used bare nx |
Use yarn nx ... |
| Build "missing dependency" | Forgot --am |
./mvnw install -pl :dotcms-core --am -DskipTests |
Puppeteer Chromium ARM64 build failure (Apple Silicon, dotcms-core-web FAILURE) |
Puppeteer can't fetch an arm64 Chromium | See "Apple Silicon / Puppeteer" below |
On M-series Macs the frontend build can fail with "The chromium binary is not
available for arm64". Fix it by pointing Puppeteer at a Homebrew Chromium and
skipping its download (add the exports to your ~/.zshrc / ~/.bashrc):
brew install chromium
export PUPPETEER_EXECUTABLE_PATH=$(which chromium)
export PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
source ~/.zshrc # or ~/.bashrccore/
├── dotCMS/ # Backend Java
│ └── src/main/java/com/
│ ├── dotcms/ # Modern, domain-driven — PREFER THIS for new code
│ └── dotmarketing/ # Legacy (15+ yrs), still very much alive
├── core-web/ # Frontend: Angular 21+ / Nx monorepo (see core-web/CLAUDE.md)
├── dotcms-integration/ # Integration tests (DB + Elasticsearch)
├── dotcms-postman/ # Postman API tests
├── bom/application/pom.xml # THE place for dependency versions
└── parent/pom.xml # Maven plugin management
Two things to internalize early:
com.dotcms.*vscom.dotmarketing.*— new work goes indotcms.*. You'll still read and patchdotmarketing.*constantly; touch it surgically and follow its existing patterns rather than modernizing wholesale mid-PR.- Backend vs frontend are separate build worlds. Many tasks are backend-only or frontend-only. Know which one your change lives in before you start.
Deeper architecture: docs/core/ARCHITECTURE_OVERVIEW.md.
⚠️ Never run the full integration suite (dotcms-integration) locally — it's 60+ minutes. Always target a class or method. Test modules also silently skip unless you pass the explicitskip=falseflag.
# One integration test class / method
./mvnw verify -pl :dotcms-integration -Dcoreit.test.skip=false -Dit.test=ContentTypeAPIImplTest
./mvnw verify -pl :dotcms-integration -Dcoreit.test.skip=false -Dit.test=MyTest#testMethodFastest loop (IDE): boot services once, then run/debug individual tests with breakpoints from IntelliJ.
just test-integration-ide # boots PostgreSQL + Elasticsearch + dotCMS
# → run/debug your test class in the IDE
just test-integration-stop # tear down when doneDebug the running app: just dev-run-debug-suspend starts dotCMS in Docker
waiting on debug port 5005 for your IDE to attach.
dotCMS uses two API testing frameworks: Postman (legacy) and Karate (the
modern replacement, in test-karate/ — prefer it for new API tests).
# Postman
just test-postman ai # a single collection (recommended)
just test-postman all # all collections (slower)
# Karate
just test-karate # default collection
just test-karate KarateCITests#defaults # a specific runner/collection
just test-karate-ide # boot services to run Karate from your IDEMore detail: docs/testing/API_TESTING.md.
cd core-web && yarn nx test <project> # Jest + SpectatorSpectator conventions (enforced in review):
// ✅ select elements by data-testid
const button = spectator.query(byTestId('submit-button'));
// ✅ set inputs via the API, never assign the property directly
spectator.setInput('inputProperty', 'value');
// ✅ test user-visible behavior, not implementation details
spectator.click(byTestId('save-button'));
expect(spectator.query(byTestId('success-message'))).toBeVisible();More detail: docs/testing/ and docs/frontend/TESTING_FRONTEND.md.
- Dependency versions →
bom/application/pom.xmlONLY. Never add versions todotCMS/pom.xml. openapi.yamlis auto-generated. Don't hand-edit it. Change the Java@Operation/@Parameterannotations, regenerate with./mvnw compile -pl :dotcms-core -DskipTests, and commit the regenerated yaml with your Java change. CI verifies they match.- Config & logging: use
Config.getStringProperty(...)andLogger.info(this, ...). NeverSystem.out,System.getProperty, orSystem.getenv. - REST
@Schemamust match the actual return type — seedotCMS/src/main/java/com/dotcms/rest/CLAUDE.md. - Security: no hardcoded secrets, validate all input, never log sensitive data.
- Progressive enhancement: when you edit a file, leave it a little better —
add generics, replace legacy logging, use modern Angular (
@ifnot*ngIf,input()not@Input()), add missing@Override/@Nullable.
- Admin password:
export DOT_INITIAL_ADMIN_PASSWORD=adminbefore first run. - Feature flags: flags are on by default. To turn one off, set it to
falsein your docker-compose file or indotmarketing-config.properties. When a feature ships, itsFF=falseentry must be removed fromdotmarketing-config.properties— removing it enables the implemented feature by default.- When referencing a flag, add the
DOT_prefix to its name (e.g. the flagFEATURE_FLAG_SEO_IMPROVEMENTSbecomesDOT_FEATURE_FLAG_SEO_IMPROVEMENTS). - The list of available flags lives in
dotCMS/src/main/java/com/dotcms/featureflag/FeatureFlagName.java.
- When referencing a flag, add the
- Starter site: a starter is a ZIP of seed content (sites, content types,
pages, assets) that dotCMS loads on first startup to give you a populated instance
instead of an empty one. Published starters live in our Artifactory:
repo.dotcms.com/artifactory/libs-release-local/com/dotcms/starter/. To pick one, set the<starter.deploy.version>property inparent/pom.xml, drop a renamedstarter.zipintodotCMS/target/starter/, or pointDOT_STARTER_DATA_LOADat a starter URL. Full details indotBackendOnboarding.md. - Edit JSPs without rebuilding: mount the webapp html dir into the Tomcat root
via a docker-compose volume (see
docs/infrastructure// the frontend docker-compose for the exact mapping). - Override language files: mount your local
dotCMS/src/main/webapp/WEB-INF/messagesinto the container the same way.
- One-time setup — GPG commit signing. Before your first commit, configure GPG
signing so your commits show as Verified on GitHub — including commits made on
your behalf by Claude Code. Follow
docs/claude/GPG_COMMIT_SIGNING.md. - Branch from up-to-date
mainfollowing the naming convention indocs/core/GIT_WORKFLOWS.md. - Commit using conventional commits (
feat:,fix:,docs:, …). - Open the PR linked to its issue. Expectations and the PR template are in
docs/core/GITHUB_ISSUE_MANAGEMENT.md. - CI / merge queue: dotCMS uses GitHub's native merge queue — the final gate
before code lands on
main. An approved, ready PR isn't merged directly. It's added to a queue where GitHub creates a temporarymerge_groupbranch containing your PR plus any PRs already ahead of it, then runs the merge-queue workflow against that combined code. Only if it passes does GitHub fast-forwardmain.- Some required checks to enter the queue: Unit / Integration / Postman test validation (test run green), security checks for code vulnerabilities, and at least 1 reviewer approval.
- Typical wait: ~1 hour average to merge.
- Pipeline reference:
docs/core/CICD_PIPELINE.md.
- When CI fails: ask Claude Code to run the
cicd-diagnosticsskill — it knows how to read dotCMS build failures and flaky tests.
You don't have to discover these the hard way — they encode dotCMS conventions:
triage— triage GitHub issues (validate, dedupe, research) before they're worked.cicd-diagnostics— diagnose failing CI runs, broken builds, flaky tests, merge-queue blocks.gh-issue-troubleshoot— take a GitHub issue from description to a proposed code fix.dotcms-github-issues(create / find / query / update) — manage issues via repo templates.vtl-migration— migrate legacy VTL custom-field templates (Dojo/Dijit →DotCustomFieldApi).check-release-rollback— assess whether a release can be safely rolled back.angular-developer+ Nx skills (nx-generate,nx-run-tasks,nx-workspace) — frontend work.
Ask Claude "what skills can help with X?" if you're unsure.
This is the highest-value section and the least discoverable by reading code. The owning team should keep it current.
- Slack channels:
#eng— general engineering.#eng-adrs— ADR (architecture decision record) discussions.#be-code-review— look here for PR review requests ("likes").#guild-*— topic-specific discussions (per guild).#feat-*— feature-related discussions.#team-*— team-specific discussions.
- Everything else (who owns what, runbooks, issue tracker & boards, release process & cadence): see our How we do Engineering doc.
- Architecture:
docs/core/ARCHITECTURE_OVERVIEW.md - Git workflows:
docs/core/GIT_WORKFLOWS.md - CI/CD:
docs/core/CICD_PIPELINE.md - Security:
docs/core/SECURITY_PRINCIPLES.md - Backend (Java/Maven):
docs/backend/— start withJAVA_STANDARDS.mdandMAVEN_BUILD_SYSTEM.md - Frontend (Angular/TS):
core-web/CLAUDE.mdanddocs/frontend/— start withANGULAR_STANDARDS.md - Testing:
docs/testing/ - The
justfileat the repo root — the source of truth forjustcommands.
Maintained by the Engineering Team. Spot something stale or wrong? Open a PR against
this file and ping the #eng or #be-code-review channels.