create-jvm
v0.1.3
Published
Scaffold Maven, Gradle and Spring Boot projects from the terminal — the IntelliJ IDEA New Project wizard as a CLI.
Maintainers
Readme
create-jvm
Scaffold Maven, Gradle and Spring Boot projects from the terminal — the IntelliJ IDEA New Project wizard as a CLI.
npx create-jvm my-app┌ create-jvm the JVM project scaffolder
│
◇ Project name
│ shop-api
│
◆ Select a project type
│ ● Spring Boot
│ ○ Maven
│ ○ Gradle
│ ○ Git template
│ ↑/↓ to navigate • Enter: confirm
│
◆ Select a build system
│ ● Maven — pom.xml
│ ○ Gradle — build.gradle / build.gradle.kts
│
◆ Select a language
│ ● Java
│ ○ Kotlin
│ ○ Groovy
│
◇ Artifact id
│ shop-api
│
◇ Group id
│ com.example
│
◇ Package name
│ com.example.shopapi
│
◆ Select packaging
│ ● Jar
│ ○ War
│
◆ Select a configuration file format
│ ● Properties — application.properties
│ ○ YAML — application.yaml
│
◆ Select a Java version
│ ● 21
│ ○ 17
│ ○ 25
│
◆ Select a Spring Boot version
│ ● 4.1.1
│ ○ 4.0.8
│
◆ Select dependencies
│ ...
│
└ Ready. Generating shop-api…This mirrors the Project Metadata panel on start.spring.io: group, artifact, package name, packaging, configuration format, Java version and Boot version are all selectable. Each prompt is pre-filled with the value that would have been derived from the project name, so the defaults are one Enter press each — and passing the matching flag skips the prompt entirely.
Why
create-vite made scaffolding a front-end project a single command. On the JVM
the equivalent is a GUI wizard, a website, or copying a project by hand. This
tool closes that gap: the same choices the IDE offers, in a terminal, with a
non-interactive mode that works in scripts and CI.
It is built on the same prompt library as create-vite, so the interaction
feels identical.
Install
Nothing to install — run it directly:
npx create-jvm my-app
pnpm create jvm my-app # the create-* convention
yarn create jvm my-app
bunx create-jvm my-appOr install it globally:
npm install -g create-jvmRequires Node.js 20.12 or newer and nothing else. You do not need Maven,
Gradle or a JDK to scaffold a project: generated projects carry their own
wrapper (mvnw / gradlew), so the build tool is fetched on first build.
Generating a Spring Boot project calls
start.spring.io, so that path needs network access. Plain Maven and Gradle projects are generated locally and work offline.
What it generates
| Project type | Build system | Languages | | --------------- | ------------------- | ------------------------ | | Spring Boot | Maven, Gradle | Java, Kotlin, Groovy | | Maven | Maven (jar / war) | Java, Kotlin, Groovy | | Gradle | Groovy or Kotlin DSL| Java, Kotlin, Groovy | | Template | whatever the repo has | — |
Every generated project includes a runnable entry point, a matching test that
passes, a .gitignore, and a README.md.
Spring Boot projects are produced by Spring Initializr
— the same service the IDE wizard and start.spring.io use — so Boot versions,
starter ids and build files always match upstream. Maven and Gradle projects are
generated locally from templates and work offline.
Usage
create-jvm [project-name] [options]Run it with no arguments for the interactive wizard. Pass flags to skip the prompts you have already decided.
Project type
create-jvm my-app # wizard
create-jvm my-app --maven # plain Maven
create-jvm my-app --gradle # plain Gradle
create-jvm my-app --spring-boot # Spring Boot
create-jvm my-app -t gh:spring-projects/spring-petclinic # remote template
# or via the create-* convention, no install needed
pnpm create jvm my-app
npm create jvm my-appCommon options
| Option | Description |
| ------ | ----------- |
| --group-id <id> | Maven groupId (default com.example) |
| --artifact-id <id> | Maven artifactId (default derived from the name) |
| --package <name> | Base package (default derived from groupId) |
| --project-version <v> | Project version (default 0.0.1-SNAPSHOT) |
| --language <l> | java, kotlin or groovy |
| --java-version <v> | 17, 21, 25 or 27 (default 21) |
| --dsl <groovy\|kotlin> | Gradle DSL |
| --packaging <jar\|war> | Archive type |
| --config-format <fmt> | Spring Boot config file: properties or yaml |
| --dep <ids> | Dependencies from the catalog, comma separated |
| --boot-dep <ids> | Spring Boot starters by Initializr id |
| --module <name> | Add a module; repeatable, implies --multi-module |
| --install | Run a build after generating to verify it |
| --dry-run | List the files without writing anything |
| --force | Write into a non-empty directory |
| --yes | Never prompt; use defaults for anything unset |
| --json | Machine-readable output |
Run create-jvm --help for the full list, including every catalog dependency id.
Examples
# Maven project with Lombok and the test stack
create-jvm orders-api --maven --dep lombok,junit5,mockito
# Spring Boot service with a driver, so the context actually starts
create-jvm orders-api --spring-boot --boot-dep web,data-jpa,postgresql,actuator
# Spring Boot with application.yaml instead of application.properties
create-jvm orders-api --spring-boot --boot-dep web --config-format yaml
# Kotlin service on Gradle with the Kotlin DSL
create-jvm billing --gradle --dsl kotlin --language kotlin --boot-dep web,actuator
# Multi-module Maven build
create-jvm platform --maven --module core --module web --module cli
# Generate, then prove it builds
create-jvm spike --spring-boot --boot-dep web --install
# Preview without touching the disk
create-jvm spike --maven --dep guava --dry-runNon-interactive and CI use
--yes (or --json, or a non-TTY stdin) disables prompting. Anything not
supplied on the command line falls back to its default, so a bare
create-jvm app --yes --spring-boot --boot-dep web is fully deterministic:
create-jvm app --yes --spring-boot --boot-dep web,actuator --jsonRemote templates
Any git repository can act as a template. The repository is downloaded as a
tarball, so no .git history is copied.
create-jvm my-app -t gh:owner/repo
create-jvm my-app -t gh:owner/repo/templates/api # subdirectory
create-jvm my-app -t gh:owner/repo#v2 # branch or tag
create-jvm my-app -t gitlab:owner/repo
create-jvm my-app -t https://github.com/owner/repoNotes on generated builds
Two details are worth knowing, because they are deliberate:
- Plugin and dependency versions are pinned. Generated POMs do not inherit from a parent, so every dependency and plugin carries an explicit version. Builds stay reproducible and do not drift when a plugin releases.
- No Java toolchain is requested. Java compilation targets the chosen
release via
options.release/maven.compiler.releaserather than a Gradle toolchain, which would make Gradle download a JDK. The build therefore works offline with whatever JDK is installed, as long as it is at least as new as the selected target.
One upstream caveat: selecting a persistence starter (data-jpa, jdbc, …)
without a database driver produces a project whose context fails to start.
Initializr accepts this combination too. create-jvm warns about it and suggests
adding a driver such as h2.
Development
npm install
npm run build # compile to dist/
npm run dev # run from source via tsx
npm test # unit tests
npm run typecheck # tsc --noEmit
npm run snapshot # hash every generated file, for output-diffingnpm run snapshot generates one project per configuration (language × build
tool × packaging × single/multi-module) and hashes every file it writes. Run it
before and after a refactor and diff the manifests to prove the generated output
did not change — the unit tests alone would not catch a subtly different
build.gradle.
The source is organised so each concern is separable:
| Path | Purpose |
| ---- | ------- |
| src/index.ts | entry point: parse, decide wizard vs flags, dispatch |
| src/args.ts | flag parsing |
| src/config.ts | flags to ProjectConfig, plus the version lookup |
| src/reporters.ts | colour handling and the human/JSON reporters |
| src/wizard.ts | wizard flow: the sequence of questions |
| src/wizard/prompts.ts | one function per question |
| src/wizard/templates.ts | the project-type list |
| src/catalog.ts | dependency catalog for plain Maven/Gradle projects |
| src/generators/ | one module per output: pom, gradle, initializr, remote, … |
| src/writer.ts | the in-memory file map that generators return |
| src/run.ts | materialise files, git init, optional build |
Generators never touch the filesystem or the network. They return a FileMap,
which is what makes --dry-run trivial, keeps the unit tests fast (the whole
suite runs in under a second), and lets a test compare generated output against
a hash baseline. Build wrappers are fetched in run.ts, where a failure can be
reported.
The Groovy and Kotlin Gradle DSLs share one structural renderer; a
GradleDialect supplies only the syntax-specific fragments, so a change to the
shared walk fixes both DSLs at once.
License
MIT
