Annotate Java methods. Get a CLI. Help text, validation, type conversion, and table formatting — all automatic.
@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.
Conventions over configuration. Sensible defaults. Full control when you need it.
Mark methods with @Command, parameters with @Option. Method signatures become your CLI contract.
Return collections with @Table for automatic tabular output. 15+ border styles from ASCII to Unicode to Markdown.
Man-page style help with NAME, SYNOPSIS, DESCRIPTION, and OPTIONS sections. Sourced from javadoc and annotations.
JSR-380 integration out of the box. Use @Required, @Exists, @Readable, or write custom validators.
Automatic string-to-type conversion for primitives, enums, URI, File, Pattern, and any class with a String constructor.
Bundle related options into reusable @Options classes. Compose them across commands for consistent interfaces.
Class-level @Command creates sub-command groups. Multiple classes can contribute to the same group.
Cross-cutting concerns with @CrestInterceptor. Timing, auditing, authentication — with custom annotation support.
@Default with system property and environment variable substitution. ${user.name} just works.
From simple commands to production-grade CLIs.
Annotate any Java method. Parameters become CLI options and positional arguments. Crest generates usage text, validates input, and converts types.
{"f", "force"}@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 /dataGroup related options into classes with @Options. Inject them as parameters to any command — no annotation needed. Use @GlobalOptions to apply to every command.
nillable = true for optional groups@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.1Domain wrapper types make CLIs self-documenting. Class names appear in help output: Usage: extend CustomerId ExpirationDate instead of String String.
@Exit exceptions with custom codesString constructor workspublic 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-31Use @Table for declarative table output from any collection. Or use TableOutput.builder() for full programmatic control with the same formatting engine.
expiration.dateTableOptions// 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 --tsv15+ border styles. Users can override at runtime with --table-border.
name version status ---------- --------- -------- tomcat 9.0.82 active activemq 5.18.3 active tomee 9.1.1 staged
┌────────────┬───────────┬──────────┐ │ name │ version │ status │ ├────────────┼───────────┼──────────┤ │ tomcat │ 9.0.82 │ active │ │ activemq │ 5.18.3 │ active │ │ tomee │ 9.1.1 │ staged │ └────────────┴───────────┴──────────┘
| name | version | status | |------------|-----------|----------| | tomcat | 9.0.82 | active | | activemq | 5.18.3 | active | | tomee | 9.1.1 | staged |
+------------+-----------+----------+ | name | version | status | +------------+-----------+----------+ | tomcat | 9.0.82 | active | | activemq | 5.18.3 | active | | tomee | 9.1.1 | staged | +------------+-----------+----------+
Three steps to your first command.
Add Crest to your Maven or Gradle project.
<dependency>
<groupId>org.tomitribe</groupId>
<artifactId>tomitribe-crest</artifactId>
</dependency>Annotate a method. Parameters become your CLI interface.
@Command
public String hello(
@Option("name")
@Default("World")
final String name) {
return "Hello, " + name;
}Crest discovers commands and handles everything else.
$ myapp hello --name=Crest
Hello, Crest
$ myapp help hello
NAME
hello