Maven Project Setup

Add a CLI module to an existing Maven project using the shade plugin and crest-maven-plugin.

Most projects already have a multi-module Maven structure with domain logic, services, and APIs. To add a CLI powered by Crest, create a dedicated module that pulls in your existing code and packages it as an executable.

Project Structure

A typical layout adds a mytool-cli module alongside your existing modules:

myproject/
  myproject-core/        # Domain logic, services
  myproject-api/         # Shared interfaces
  myproject-cli/         # CLI module (new)
    src/main/java/
      com/example/cli/
        MyCommands.java  # @Command methods
        MyLoader.java    # Loader implementation
    pom.xml
  pom.xml                # Parent POM

CLI Module POM

The CLI module’s pom.xml has three parts: dependencies on Crest and your project modules, the shade plugin to create an uber jar, and the crest-maven-plugin to generate descriptors and create the executable.

<project>
    <parent>
        <groupId>com.example</groupId>
        <artifactId>myproject</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>myproject-cli</artifactId>

    <dependencies>
        <!-- Your project modules -->
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>myproject-core</artifactId>
            <version>${project.version}</version>
        </dependency>

        <!-- Crest runtime -->
        <dependency>
            <groupId>org.tomitribe</groupId>
            <artifactId>tomitribe-crest</artifactId>
        </dependency>
        <dependency>
            <groupId>org.tomitribe</groupId>
            <artifactId>tomitribe-crest-api</artifactId>
        </dependency>

        <!-- Classpath scanning (optional — see Loader below) -->
        <dependency>
            <groupId>org.tomitribe</groupId>
            <artifactId>tomitribe-crest-xbean</artifactId>
        </dependency>
    </dependencies>

    <build>
        <defaultGoal>package</defaultGoal>
        <plugins>
            <!-- 1. Create an uber jar with all dependencies -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.2.4</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <shadedArtifactAttached>true</shadedArtifactAttached>
                            <shadedClassifierName>all</shadedClassifierName>
                            <dependencyReducedPomLocation>
                                ${project.build.directory}/reduced-pom.xml
                            </dependencyReducedPomLocation>
                            <transformers>
                                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>org.tomitribe.crest.Main</mainClass>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <!-- 2. Generate descriptors + create executable -->
            <plugin>
                <groupId>org.tomitribe</groupId>
                <artifactId>crest-maven-plugin</artifactId>
                <version>${crest.version}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>descriptor</goal>
                            <goal>executable</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

What Each Plugin Does

maven-shade-plugin

Creates a single jar containing all dependencies (your modules, Crest, transitive deps). The ManifestResourceTransformer sets org.tomitribe.crest.Main as the entry point so java -jar works. The shadedArtifactAttached option keeps the original jar and produces the uber jar with an -all classifier.

crest-maven-plugin

Provides two goals:

descriptor — Runs at compile time to scan your compiled classes for @Command, @Editor, and @CrestInterceptor annotations and generate a crest-commands.txt file. It also generates the META-INF/services/org.tomitribe.crest.api.Loader service file pointing to the built-in CrestCommandsLoader, unless you provide your own.

executable — Runs at package time to prepend a shell stub to the shaded jar, making it directly executable on Unix systems without typing java -jar. After mvn package, you get an executable named after your artifact:

$ ./myproject-cli --help

The default shell stub passes -Dcmd="$0" so Crest can show the script name in help output, and $JAVA_OPTS so users can set JVM flags via the environment. The executable is attached as a build artifact by default, so mvn install and mvn deploy publish it alongside the jar.

Descriptor Goal Configuration

The descriptor goal scans your compiled classes and generates crest-commands.txt. Use excludes to remove classes from the scan results and includes to add classes that the scanner wouldn’t find:

<plugin>
    <groupId>org.tomitribe</groupId>
    <artifactId>crest-maven-plugin</artifactId>
    <version>${crest.version}</version>
    <executions>
        <execution>
            <goals>
                <goal>descriptor</goal>
                <goal>executable</goal>
            </goals>
            <configuration>
                <excludes>
                    <exclude>com.example.internal.*</exclude>
                    <exclude>com.example.DebugCommands</exclude>
                </excludes>
                <includes>
                    <include>com.example.extra.ManualCommand</include>
                </includes>
            </configuration>
        </execution>
    </executions>
</plugin>

Exclude patterns support * as a wildcard that matches any characters. For example, com.example.internal.* matches all classes in that package and its sub-packages.

Executable Goal Configuration

All parameters have sensible defaults. Override them only when needed:

ParameterDefaultDescription
flags-Dcmd="$0" $JAVA_OPTSJVM arguments embedded in the shell stub
programFile${project.artifactId}Name of the generated executable
classifierallClassifier of the shaded jar to use as input
attachProgramFiletrueAttach the executable as a build artifact
scriptFilePath to a custom shell script instead of the default stub
inputFileSpecific jar file to use instead of finding by classifier

Example with custom flags:

<plugin>
    <groupId>org.tomitribe</groupId>
    <artifactId>crest-maven-plugin</artifactId>
    <version>${crest.version}</version>
    <executions>
        <execution>
            <goals>
                <goal>descriptor</goal>
                <goal>executable</goal>
            </goals>
            <configuration>
                <flags>-Dcmd="$0" $JAVA_OPTS -Xmx2G</flags>
            </configuration>
        </execution>
    </executions>
</plugin>

Build and Run

mvn package
./myproject-cli/target/myproject-cli help

The target/ directory will contain:

  • myproject-cli-1.0-SNAPSHOT.jar — original jar
  • myproject-cli-1.0-SNAPSHOT-all.jar — uber jar with all dependencies
  • myproject-cli — directly executable binary

Command Discovery

Crest needs to find your @Command, @Editor, and @CrestInterceptor classes. You have three options:

The descriptor goal scans your compiled classes at build time and generates a crest-commands.txt file plus the service loader wiring. No runtime scanning, no extra dependencies. Use excludes and includes in the plugin configuration if you need to control what’s discovered.

Option B: Explicit Loader

For full control, create a Loader that lists your classes explicitly:

public class MyLoader implements Loader {
    @Override
    public Iterator<Class<?>> iterator() {
        return Loader.of(
            DeployCommands.class,
            ConfigCommands.class,
            StatusCommands.class,
            AuditInterceptor.class,
            InstantEditor.class
        ).iterator();
    }
}

Register it in META-INF/services/org.tomitribe.crest.api.Loader:

com.example.cli.MyLoader

When an explicit Loader is present, it is authoritative — Crest uses it and does not fall back to other discovery mechanisms.

Option C: Main.builder()

For full programmatic control, write your own main() method instead of using org.tomitribe.crest.Main:

public class MyCli {
    public static void main(String[] args) throws Exception {
        new Main.builder()
                .command(DeployCommands.class)
                .command(ConfigCommands.class)
                .load(AuditInterceptor.class)
                .load(InstantEditor.class)
                .name("mytool")
                .version("1.0")
                .build()
                .run(args);
    }
}

Then update the shade plugin’s <mainClass> to com.example.cli.MyCli.