Open Source · Apache 2.0

Command-line API
styled after JAX-RS

Annotate Java methods. Get a CLI. Help text, validation, type conversion, and table formatting — all automatic.

DeployCommands.java
@Command("deploy")
@Table(fields = "name version status", sort = "name")
public Stream<App> deploy(
        @Option("target") @Required final URI target,
        @Option("dry-run") @Default("false") final boolean dryRun,
        @Option("tag") final List<String> tags,
        final Config config) {
    // Your logic here. Crest handles the rest.
}
90% of writing scripts is parsing and validating user input. Crest handles that so you can get to the real work.
0
Lines of parsing code
100%
Annotation-driven
15+
Table border styles
Auto
Help & man pages

Everything a CLI needs

Conventions over configuration. Sensible defaults. Full control when you need it.

See it in action

From simple commands to production-grade CLIs.

A method is a command

Annotate any Java method. Parameters become CLI options and positional arguments. Crest generates usage text, validates input, and converts types.

  • Aliases via array values: {"f", "force"}
  • Defaults with variable substitution
  • Unannotated params are positional args
  • Return value printed to stdout
@Command
public String upload(
        @Option("customer-id") final String id,
        @Option({"f", "force"}) final boolean force,
        @Option("skip")
        @Default(".DS_Store|cust.*")
            final Pattern skip,
        final URI source) {

    return "Uploaded " + source;
}

// CLI: upload --customer-id acme -f /data

Reusable option bundles

Group related options into classes with @Options. Inject them as parameters to any command — no annotation needed. Use @GlobalOptions to apply to every command.

  • Constructor parameters define options
  • Compose multiple option classes
  • Use nillable = true for optional groups
  • Options merge into the command's help
@Options
public class Config {
    public Config(
        @Option("config") @Default("default")
            final String name,
        @Option("env") @Default("prod")
            final String env) { ... }
}

@Command
public void deploy(
        final Config config,
        @Option("version") final String ver) {
    // config.name, config.env available
}

// CLI: deploy --config=staging --env=dev --version=2.1

Self-documenting types

Domain wrapper types make CLIs self-documenting. Class names appear in help output: Usage: extend CustomerId ExpirationDate instead of String String.

  • Validation in the constructor
  • @Exit exceptions with custom codes
  • Bean Validation annotations supported
  • Any class with String constructor works
public class CustomerId {
    private final String id;

    public CustomerId(final String id) {
        if (id.length() != 18 || !id.startsWith("001"))
            throw new InvalidFormatException(id);
        this.id = id;
    }

    @Exit(1)
    public static class InvalidFormatException
            extends RuntimeException { ... }
}

@Command
public PrintOutput extend(
        final CustomerId customer,
        final ExpirationDate expiration) { ... }

// CLI: extend 001ABC123456789012 2025-12-31

Declarative or programmatic

Use @Table for declarative table output from any collection. Or use TableOutput.builder() for full programmatic control with the same formatting engine.

  • 15+ border styles built in
  • Nested fields: expiration.date
  • User-overridable via TableOptions
  • Custom cell formatting via PropertyEditors
// Declarative
@Command
@Table(fields = "id name status",
       sort = "name",
       border = Border.unicodeSingle)
public Stream<Package> list() { ... }

// Programmatic
@Command
public TableOutput report(
        final TableOptions tableOptions) {
    return TableOutput.builder()
            .data(loadAccounts())
            .fields("id name email")
            .sort("name")
            .options(tableOptions)
            .build();
}

// CLI: list --table-border=unicodeSingle --tsv

Beautiful terminal tables

15+ border styles. Users can override at runtime with --table-border.

Border.asciiCompact

 name        version   status
---------- --------- --------
 tomcat      9.0.82    active
 activemq    5.18.3    active
 tomee       9.1.1     staged

Border.unicodeSingle

┌────────────┬───────────┬──────────┐
│ name       │ version   │ status   │
├────────────┼───────────┼──────────┤
│ tomcat     │ 9.0.82    │ active   │
│ activemq   │ 5.18.3    │ active   │
│ tomee      │ 9.1.1     │ staged   │
└────────────┴───────────┴──────────┘

Border.githubMarkdown

| name       | version   | status   |
|------------|-----------|----------|
| tomcat     | 9.0.82    | active   |
| activemq   | 5.18.3    | active   |
| tomee      | 9.1.1     | staged   |

Border.mysqlStyle

+------------+-----------+----------+
| name       | version   | status   |
+------------+-----------+----------+
| tomcat     | 9.0.82    | active   |
| activemq   | 5.18.3    | active   |
| tomee      | 9.1.1     | staged   |
+------------+-----------+----------+

Up and running in minutes

Three steps to your first command.

1

Add the dependency

Add Crest to your Maven or Gradle project.

<dependency>
  <groupId>org.tomitribe</groupId>
  <artifactId>tomitribe-crest</artifactId>
</dependency>
2

Write a command

Annotate a method. Parameters become your CLI interface.

@Command
public String hello(
  @Option("name")
  @Default("World")
  final String name) {
  return "Hello, " + name;
}
3

Run it

Crest discovers commands and handles everything else.

$ myapp hello --name=Crest
Hello, Crest

$ myapp help hello
NAME
  hello
Read the Documentation