vermilingua is a Maven plugin that builds WebObjects applications and frameworks. It is a replacement for the wolifecycle plugin, written from scratch rather than wrapped around the old WOProject Ant tasks, and it produces a self-contained .woa bundle: no NEXT_ROOT, no system-wide WebObjects installation on the build machine or the server. It builds classic WebObjects projects, Wonder and wonder-slim projects, and, since it doesn't care what the Java inside is, ng-objects applications too.

This guide takes a project from an empty directory to a running bundle. If you're migrating from wolifecycle, skip to the migration section; the rest still applies but you already have most of it.

1. What you need first

A JDK and Maven on the command line, and Maven pointed at the WOCommunity repository, which is where the WebObjects and Wonder artifacts live. If you followed the development setup guide you have all three; Parslips writes the repository into ~/.m2/settings.xml from its Maven preference page. Without Parslips, add the profile by hand:

<settings>
  <profiles>
    <profile>
      <id>wocommunity</id>
      <activation><activeByDefault>true</activeByDefault></activation>
      <repositories>
        <repository>
          <id>wocommunity.releases</id>
          <url>https://maven.wocommunity.org/content/groups/public</url>
        </repository>
        <repository>
          <id>wocommunity.snapshots</id>
          <url>https://maven.wocommunity.org/content/groups/public-snapshots</url>
          <snapshots><enabled>true</enabled></snapshots>
        </repository>
      </repositories>
    </profile>
  </profiles>
</settings>

The most common build failure on the mailing lists, "Unresolveable build extension" or "could not be resolved" for a plugin or a WebObjects artifact, is this file missing or the profile not active. vermilingua itself is on Maven Central, so it resolves either way; the frameworks it builds against are not.

2. The pom

Two things make a pom a WebObjects pom: the packaging, and the plugin registered as a build extension so Maven knows what that packaging means.

<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example</groupId>
  <artifactId>MyApp</artifactId>
  <version>1.0.0-SNAPSHOT</version>
  <packaging>woapplication</packaging>

  <properties>
    <maven.compiler.release>25</maven.compiler.release>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>is.rebbi.slim</groupId>
      <artifactId>ERExtensions</artifactId>
      <version>8.0.4</version>
    </dependency>
    <dependency>
      <groupId>is.rebbi.slim</groupId>
      <artifactId>ERLoggingReload4j</artifactId>
      <version>8.0.4</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>is.rebbi</groupId>
        <artifactId>vermilingua-maven-plugin</artifactId>
        <version>1.1.7</version>
        <extensions>true</extensions>
      </plugin>
    </plugins>
  </build>
</project>

Packaging is woapplication for an application and woframework for a framework. <extensions>true</extensions> is not optional; it is what binds the packaging to the plugin's lifecycle. The dependencies shown are wonder-slim, which brings WebObjects itself in transitively. A classic Wonder project lists ERExtensions from the wonder.core group and JavaWebObjects from com.webobjects instead; vermilingua doesn't mind which.

3. Project layout

vermilingua expects the standard Maven layout, with one deliberate change: WebObjects bundle resources have their own folder, so that src/main/resources means what it means in every other Java project, the classpath.

MyApp/
  pom.xml
  build.properties
  src/main/java/                  Java sources
  src/main/components/            .wo bundles and standalone .html templates
  src/main/woresources/           WebObjects bundle resources: .eomodeld, Properties, .plist, …
  src/main/webserver-resources/   static files served to the browser: CSS, images, JavaScript
  src/main/resources/             classpath resources, as in any Java project

Any folder structure under components is flattened into the bundle, since WebObjects cannot find components in subfolders at runtime; organise them however you like. Localised .lproj folders are preserved. If a project has the old "Fluffy Bunny" layout, Sources, Resources, Components and WebServerResources at the top level, the plugin's woresourcesPath, componentsPath and webserverResourcesPath parameters point it there, with Maven's sourceDirectory set to Sources; the README has the exact configuration.

4. build.properties

A small file at the project root that both the build and the editor read. The essentials for an application:

project.name=MyApp
project.type=application
principalClass=com.example.Application
launch.jvm=java
launch.jvmOptions=-Xmx512m

The launch.* keys end up in the bundle's config.txt and control how the launch script starts the JVM. They can be set here, overridden at build time with mvn package -Dlaunch.jvm=/opt/jdk-25/bin/java, or at launch time with ./MyApp -launch.jvm=…, later layers winning. The --add-opens clauses that WebObjects needs on a modern JDK are included by default, so you don't have to remember them. Parslips reads project.base and the inline-binding keys from the same file.

5. Building and running

mvn package
./target/MyApp.woa/MyApp -WOPort 1200

That's the whole loop. The result under target is a self-contained bundle:

MyApp.woa
  MyApp                launch script; runs anywhere with a JDK, no NEXT_ROOT
  config.txt           jvm, jvmOptions, principalClass
  classpath.txt
  Contents/
    Info.plist
    WebServerResources/
    Frameworks/          each framework's WebServerResources
    Resources/Java/      myapp.jar and every dependency jar

Every jar the application needs is inside the bundle, which is why the server needs nothing installed beyond a JDK. The launch script is plain shell; on Windows it runs under WSL or Git Bash. Building again without clean overwrites resources in the existing bundle, so a stale file can't survive a rebuild.

6. Frameworks

A framework project uses woframework packaging and the same layout. The build produces a Maven jar that carries the framework's components, resources and WebServerResources inside it, plus an Info.plist marking it as a WebObjects framework. Applications depend on it as an ordinary Maven dependency, and vermilingua unpacks the WebServerResources into the application bundle's Contents/Frameworks at build time. There is no separate .framework directory to install anywhere, and no old-style framework bundles at all: vermilingua builds jar frameworks only.

7. Deploying the bundle

Copy the .woa to the server and point wotaskd at it; the deployment guides cover the server side. Two plugin parameters help with the copying: createArchives produces a tar.gz of the bundle, and performSplit produces a separate WebServerResources bundle for setups where Apache serves the static files. Neither is on by default, because with modulo the application serves its own resources and neither is needed.

Since 1.1.7 the plugin can also push the build straight to JavaMonitor:

mvn package vermilingua:deploy -Ddeploy.password=…

With deploy.appName, deploy.monitorHost and deploy.password set in build.properties or as -D overrides, the goal archives the bundle, posts it to JavaMonitor's deploy endpoint, and JavaMonitor hands it to wotaskd on every host the application runs on. It is marked experimental: it works and is in real use, but the details may still move.

8. Migrating from wolifecycle

For most projects the migration is the plugin element. Replace wolifecycle-maven-plugin with the vermilingua element from section 2, then check these:

  • Resources move from src/main/resources to src/main/woresources, unless you set woresourcesPath to keep them where they are. Moving them is the better choice.
  • Drop flattenComponents and flattenResources from the configuration; components are always flattened, resources never are. .patternset files are ignored; the build relies on the folder structure.
  • Not supported: .war builds for servlet deployment, and old-style .framework bundles. If you need either, stay on wolifecycle for that project.
  • NEXT_ROOT is no longer read, and the launch script no longer passes -DWORootDirectory or -DWOLocalRootDirectory. A deployment that relied on a system-wide WebObjects install under /Library/WebObjects stops needing it.
  • finalName only changes the bundle's folder name; the inside is identical regardless.

9. When it doesn't work

  • "Unresolveable build extension" or an artifact that "could not be resolved": the WOCommunity repository isn't configured, or the profile isn't active. See section 1. Fix the command-line build first; Eclipse only complicates the diagnosis.
  • NoClassDefFoundError at runtime for a class that compiled fine: a dependency with the wrong scope, or a framework jar that isn't in Contents/Resources/Java. Check classpath.txt in the bundle; it lists exactly what the launch script puts on the classpath.
  • The main bundle can't be found when launching from Eclipse, typically NSProperties._mainBundleName() returning null: the project needs build.properties at its root with project.name set, and it must be imported as a Maven project so Eclipse's output folder matches Maven's. If the command-line build runs, this is an Eclipse setup problem, not a build problem.
  • The launch script fails with EACCES or systemd reports status=203/EXEC: an old plugin version set the execute bit for the owner only, and the bundle was copied by root while the app runs as another user. 1.1.7 sets it for everyone; until then, chmod a+x MyApp.woa/MyApp.
  • Static resources 404 on the server: the application serves its own WebServerResources unless you built with performSplit and configured Apache to serve them. Use one or the other, not half of each.

Everything above is current as of vermilingua 1.1.7. The release notes say what changed since, and the repository takes issues.