The deploy goal is new in vermilingua 1.1.7 and marked experimental: it works and is in daily use on our own servers, but details may change in coming releases. It needs the JavaMonitor and wotaskd from wonder-slim-deployment, which is where the receiving end lives.

Once a server is set up as in the deployment guides, getting a new build onto it used to mean copying the bundle by hand, moving the old one aside and restarting the instances, on every host the application runs on. vermilingua:deploy does all of that from the build machine:

mvn package vermilingua:deploy -Ddeploy.password=…

The goal packs the freshly built .woa into a tar.gz and posts it to JavaMonitor. JavaMonitor hands the archive to the wotaskd on every host that runs an instance of the application. Each wotaskd unpacks it beside the current bundle, moves the current bundle aside, moves the new one into place, and restarts the instances it was running. The response comes back as a report, one section per host, and the build fails if any host failed.

1. What has to be true first

  • The project builds with vermilingua 1.1.7 or later and has woapplication packaging. In a multi-module build the goal skips every module that isn't an application.
  • The servers run wonder-slim-deployment's JavaMonitor and wotaskd, and the application is already defined in JavaMonitor with at least one instance. The deploy finds its hosts through the instances; an application with none is refused. The first deployment of a new application is therefore still by hand: install the bundle, add the application and its instances in JavaMonitor, and every deployment after that is the command above.
  • The build machine can reach JavaMonitor, on its port or through a front end; see the host setting.
  • The hosts have tar, which every Linux does. wotaskd uses it rather than a Java implementation so the launch script's execute bit and any symlinks survive.

2. Configuration

Three settings, read the same way as the launch.* settings: from build.properties, overridden by an environment file, overridden by -D properties on the command line.

deploy.monitorHost = monitor.example.com
deploy.appName = MyApp
SettingMeaning
deploy.monitorHostWhere JavaMonitor is. A bare hostname uses port 56789; host:port sets another; a full https://… base URL deploys through a front end that proxies JavaMonitor, so its port never has to be opened to the build machine.
deploy.appNameThe name JavaMonitor knows the application by. Defaults to the bundle name, which is usually right; set it when the two differ.
deploy.passwordThe stack password JavaMonitor's admin actions require. Pass it as -Ddeploy.password=… rather than writing it into a file that lives in a repository.

Per-environment values go in build.properties.<env>, selected with -Dbuild.env=<env>. A project that deploys to a staging monitor and a production monitor keeps build.properties.staging and build.properties.prod with their own deploy.monitorHost, and the command line picks one:

mvn package vermilingua:deploy -Dbuild.env=prod -Ddeploy.password=…

3. Running it

package first, then deploy, in the same invocation or two; the goal refuses to run if there is no bundle under target. A successful run prints the size of the archive, where it went, and each host's report:

[INFO] Deploying MyApp (41,203,118 bytes) to http://monitor.example.com:56789
[INFO]   hz1.example.com:
[INFO]     unpacked MyApp.woa (41203118 bytes)
[INFO]     previous bundle kept as xMyApp_2026_09_14_10_42_07.woa
[INFO]     pruned 1 older build, keeping 5 (WOTaskd.deploy.retainedBuilds)
[INFO]     restarted MyApp-1
[INFO]     restarted MyApp-2
[INFO] Deployed MyApp

Anything other than HTTP 200 from JavaMonitor fails the goal, and JavaMonitor answers 500 if any host reported a failure, so a broken deployment breaks the build rather than passing quietly. The upload has a five-minute timeout, generous for any bundle that should exist.

4. From a build server

This is where the goal earns its keep: a push to the main branch becomes a deployment. In Jenkins the Maven goals for our own sites are:

clean package vermilingua:deploy -Dlaunch.jvm=/opt/jdk-26/bin/java -Ddeploy.password=${DEPLOY_PASSWORD}

with the password held as a Jenkins credential and injected into the environment, never in the job's visible configuration. launch.jvm is set at the same time so the bundle's launch script points at the server's JDK rather than the build machine's; the two settings travel together for the same reason, they describe the target rather than the source.

5. What happens on each host

wotaskd's deployer works in a fixed order, and the order is the point:

  1. Unpack into a staging directory beside the bundle. Same filesystem, so the moves that follow are atomic renames rather than copies.
  2. Swap. The current MyApp.woa is renamed to xMyApp_<timestamp>.woa and the new bundle takes its name. Instances are still running at this point: a JVM holds its jars open by descriptor, so renaming the directory under it does no harm, and anything that starts the instance from now on, the deploy or wotaskd's auto-recover sweep, starts the new build.
  3. Prune. Moved-aside builds beyond the newest five are deleted; WOTaskd.deploy.retainedBuilds on wotaskd changes the count, negative keeps everything. A build that won't delete is reported and left; housekeeping never stops a deployment.
  4. Bounce. Every instance that was running is terminated, waited for, and started again. An instance that still holds its port after the wait is reported as not restarted rather than started on top. A host with no running instances gets the new bundle and nothing else.

The report you see in the build output is these steps, one line each, per host.

6. Downtime and rollback

This first iteration is a full bounce: on each host every instance stops and starts around the same moment, so the application is unavailable for the seconds a restart takes. For most of our applications that is fine and happens at a quiet hour. When it isn't, stop and start the instances yourself around the swap, through JavaMonitor's pages or its admin/stop and admin/start actions, or deploy to one host at a time. Graceful and rolling variants are on the list.

Rolling back is the swap in reverse, because the previous bundle is still there: on the host, rename the current bundle aside, rename xMyApp_<timestamp>.woa back to MyApp.woa, and bounce the instances from JavaMonitor. The last five builds are kept for exactly this.

7. The same thing by hand

The goal is a thin client for one HTTP request, so anything that can post a file can deploy. Useful from a machine without Maven, or to see exactly what the goal does:

tar -czf MyApp.tar.gz -C target MyApp.woa
curl -X POST -H 'Content-Type: application/octet-stream' --data-binary @MyApp.tar.gz \
  "http://monitor.example.com:56789/Apps/WebObjects/JavaMonitor.woa/admin/deploy?type=app&name=MyApp&pw=…"

The content type is not optional. Without it curl declares the body form-encoded, and JavaMonitor tries to parse a forty-megabyte archive as form fields.

8. When it doesn't work

  • "No monitor to deploy to": deploy.monitorHost isn't set in any of the three places. Check the environment file's name matches -Dbuild.env.
  • "No application bundle at target/…": run package in the same command, or before it.
  • "Unknown application", HTTP 404: JavaMonitor has no application by that name. The default is the bundle name; set deploy.appName to what JavaMonitor shows.
  • "has no instances — nothing to deploy to", HTTP 406: add at least one instance in JavaMonitor. The hosts to deploy to are read from the instances.
  • A password error: the stack password guards every admin action; pass -Ddeploy.password. The password currently travels as a query parameter, which front-end access logs may record; deploying over a private network or through a front end you control keeps it out of the wrong logs until the endpoint learns to take it as a header.
  • Connection refused or a timeout: the build machine can't reach port 56789. Open it to the build machine only, or set deploy.monitorHost to an https:// front end that proxies JavaMonitor.
  • "still holds port … — not restarted" in a host's report: an instance didn't leave its port within the wait. The new bundle is in place; start the instance from JavaMonitor once the old process is gone.

Everything above matches vermilingua 1.1.7 and the current wonder-slim-deployment. The release notes say what changed since.