name: mule-headless-dev
description: >-
Run, deploy, and iterate on a MuleSoft (Mule 4) application on a REAL local runtime
WITHOUT Anypoint Studio — a headless build → deploy → test → repeat loop for fast,
autonomous Mule development. Use this whenever you need to actually run a Mule app
locally, redeploy it after a code/config change, smoke-test a flow, reproduce or
confirm a runtime bug, or set up a standalone Mule runtime — even if the user just
says "test the mule app", "run my mule flow", "deploy this locally", "redeploy",
"why isn't my change showing up", or "spin up a mule runtime". Covers Windows, Linux,
and macOS, hot-redeploy vs full restart, and the common runtime gotchas (JDK version,
Studio-copy self-termination, boot timeouts, stale classloaders, object-store state).
Prefer this skill over hand-rolling mvn/mule commands for anything that runs a
Mule app at runtime.
Mule headless dev loop (no Anypoint Studio)
Build a Mule app, deploy it to a real standalone Mule runtime, test it, and iterate — all from the command line. This is the fast, scriptable, autonomous alternative to clicking "Run" in Anypoint Studio, and it's the only practical way to do a tight edit → deploy → test loop unattended.
When to use this
- You need to actually run a Mule app at runtime (not just unit tests) — smoke-test a flow, hit an HTTP listener, confirm a fix behaves correctly, reproduce a runtime bug.
- You want a fast iterate loop: change code/config → redeploy in seconds → re-test.
- You're working without Studio (CI, a headless box, an agent session, or just speed).
- You need to stand up a local Mule runtime in the first place.
The mental model (read this once)
mvn clean packageon a Mule project producestarget/<artifactId>-<version>-mule-application.jar.- A standalone Mule runtime continuously watches its
apps/folder. Drop a jar in → it deploys. Replace the jar (newer timestamp) → it hot-redeploys in place in a few seconds, no restart. - So the loop is: keep one runtime running, and iterate by rebuilding + copying the jar. Test by exercising the app (HTTP calls, the project's test scripts, etc.).
- Keep exactly ONE runtime per app. If several exist (a leftover standalone, Studio's embedded
runtime, two copies in different folders), you can rebuild + deploy into one while the port you're
testing is served by another — your change silently never lands, and it looks exactly like a stale
classloader. The deploy target must be the runtime that owns the test port (find it via the
port → PID →
-Dmule.homequery inreferences/troubleshooting.md§11). Retire the rest. - Full restarts are rarely needed — but they ARE sometimes the only way to be sure a change is live (see "Hot-redeploy vs full restart" below). Reach for one when in doubt.
Prerequisites
Check these first; the setup script verifies them too.
- JDK 17. Mule 4.9+ runs on Java 17. Many machines default
javato 8 or 11 — that will fail withUnsupportedClassVersionError. You must pointJAVA_HOMEat a JDK 17 for everymvnandmuleinvocation. (The bundled scripts auto-detect a JDK 17 and set this for you.) - Maven 3.9+.
- Anypoint Maven credentials in
~/.m2/settings.xml— needed to download the EE runtime and to resolve EE connectors / Exchange dependencies. Required if the app uses EE features (ee:transform/DataWeave, most connectors). If you can alreadymvn clean packagethe project, you have these.
One-time setup (per machine)
Run scripts/setup.sh (Linux/macOS) or scripts/setup.ps1 (Windows). It detects the OS and a
JDK 17, downloads the runtime, extracts it, and configures it. Or do it by hand:
- Download a CLEAN standalone Mule EE distribution via Maven (one zip works on every OS — it
bundles Windows/Linux/macOS launchers + wrapper binaries):
Pick amvn dependency:get -Dartifact=com.mulesoft.mule.distributions:mule-ee-distribution-standalone:<version>:zip<version>that matches your app's target runtime (e.g.4.11.4). It runs on a 30-day evaluation license out of the box — fine for local dev. - Extract the zip to a user-writable directory (e.g.
~/mule-eeorD:\mule-ee). NOT underC:\Program Files/ a path that needs admin, or hot-deploy will fail on permissions. - Configure
conf/wrapper.conf:wrapper.ping.timeout=300— the default30can kill a slow or resource-contended boot (the wrapper pings the JVM; if it can't answer within the window during a heavy boot, it doesDUMP,RESTARTand the runtime dies ~30 s in).- Add any system properties / secure properties your app needs as
wrapper.java.additional.N=-Dkey=value(e.g.-Dclaude.apiKey=…).ignore_sequence_gaps=TRUEis set, so any freeN(e.g. 101) works.
- Deploy the app:
mvn clean packagethen copytarget/*-mule-application.jarinto<runtime>/apps/.
CRITICAL — do NOT use Anypoint Studio's embedded runtime as your standalone runtime. The runtime bundled inside
…/AnypointStudio/plugins/…server.<ver>.ee…/muleis coupled to the IDE: run standalone it deploys the app and then self-terminates ~200 ms later (AgentStudioManagementServiceteardown → shutdown hook), in every launch mode. Always download a fresh standalone distribution.
The loop
- Start the runtime (once):
scripts/start.{sh,ps1}. This pins JDK 17 and launches the runtime.- Linux/macOS:
bin/mule startdaemonizes cleanly; the runtime survives the shell.bin/mule stopto stop. - Windows: run
bin\mule.bat consoleunder a background shell so it persists. (Do NOT use the Windows service —mule.bat install/starthits the Windows SCM ~30 s service-start timeout vs Mule's slower boot and the SCM aborts it. Console-under-a-background-shell is the reliable path; no admin needed.)
- Linux/macOS:
- Redeploy after a change:
scripts/redeploy.{sh,ps1}→mvn clean package, copy the jar intoapps/, then wait until the runtime logsStarted app '<name>'(and, if given, an app health endpoint returns 200). - Test: exercise the app — curl its HTTP listener, run the project's
*-testscripts, etc. - Repeat. Edit → redeploy → test.
Platform specifics
| Windows | Linux / macOS | |
|---|---|---|
| Launcher | bin\mule.bat |
bin/mule |
| Keep it running | console mode under a background shell (service path fails: SCM 30 s timeout) |
mule start daemonizes — survives the shell |
| Stop | mule.bat stop if started as console it ignores; kill the wrapper/javaw (scripts/stop.ps1) |
mule stop |
| Find JDK 17 | Eclipse Adoptium under Program Files; else winget install EclipseAdoptium.Temurin.17.JDK |
mac: /usr/libexec/java_home -v 17; linux: update-alternatives, sdkman, or /usr/lib/jvm/java-17-* |
| Process tools | taskkill, Get-CimInstance Win32_Process |
ps, pkill, kill |
Windows: prefer the
.ps1scripts. Run under PowerShell, not Git-Bash. Git-Bash/MSYS rewrites/-leading paths and some env values into Windows paths when it calls a native exe (e.g./a2a→C:/Program Files/Git/a2a), which silently corrupts args/-Dprops/baked config. The.ps1scripts set env vars natively and avoid it. If you must use.shunder Git-Bash, seereferences/troubleshooting.md§12.
Readiness & deploy-result detection (works for ANY Mule app)
The reliable, app-agnostic signal is the runtime log at <runtime>/logs/mule_ee.log:
- Success: a line
Started app '<artifactId>...'. - Failure:
Failed to deploy,DeploymentException, or... could not be deployed.
If the app exposes an HTTP listener, also poll its endpoint (or /console, or any known path) for a
200 to confirm it's actually serving. The bundled redeploy script does both.
Hot-redeploy vs FULL restart — when in doubt, restart
Hot-deploy (copy the jar) is fast and usually correct. But Mule reuses classloaders and caches some resources, so occasionally a redeploy does not reflect your latest code or config — you'll see stale behavior, a config/property change that "isn't picked up," or generally weird state. This is the single most important troubleshooting reflex: when you're not certain a change is actually live, do a FULL restart / full redeploy. It's cheap and removes all doubt.
Full clean redeploy (see references/troubleshooting.md for the scripted version):
- Stop the runtime.
- Remove the exploded app in
apps/: delete both<runtime>/apps/<name>/(the exploded directory) and<runtime>/apps/<name>-anchor.txt. This forces a clean re-explode of the jar (not a patch of the old one). - Copy the freshly built jar into
apps/. - Start the runtime and watch for
Started app.
scripts/restart.{sh,ps1} does exactly this. Use it whenever a hot-redeploy's result is suspicious, when
you changed something the hot path doesn't reliably re-read (some META-INF/resource files, log4j2 config,
certain global configs), or just to get a known-good baseline before chasing a bug.
Also beware persistent state between test runs: persistent Object Stores and other on-disk state live
under <runtime>/.mule/ (and survive restarts). A test that assumes a clean slate can be polluted by a
prior run. If results look "remembered," clear the relevant Object Store data (or <runtime>/.mule/<app>/)
before re-testing.
Troubleshooting
references/troubleshooting.md has the full catalog with concrete fixes:
boot dies ~30 s, Studio-copy self-termination, UnsupportedClassVersionError (wrong JDK), port already in
use, stale hot-reload → full restart, Object-Store state leakage, license/eval, mule.bat/JAVA_HOME not
found, and clean-vs-hot redeploy. Read it when anything doesn't behave as expected.
Scripts reference
All scripts are parameterized (no hardcoded paths). They take options via flags or environment variables;
run any with -h/--help (or read the header). Common settings: the runtime home (MULE_RUNTIME), the
Maven project dir (MULE_PROJECT, default = current dir), the JDK 17 home (MULE_JDK17, auto-detected),
and an optional app health URL for readiness.
scripts/setup.{sh,ps1}— download + extract + configure a clean standalone runtime (one-time).scripts/start.{sh,ps1}— start the runtime (Unix daemon / Windows console-in-background) under JDK 17.scripts/redeploy.{sh,ps1}—mvn clean package→ copy jar → wait until deployed. The iterate primitive.scripts/restart.{sh,ps1}— FULL clean restart (stop → wipe exploded app + anchor → copy jar → start).scripts/stop.{sh,ps1}— stop the runtime.