You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This issue is the Community over Code Glasgow 2026 working document for one Grails application generator and one agreed CLI command structure. It inventories how the two generators and the several CLIs actually work today. It does not prescribe combining them, deprecating one, or mixing them.
The three issues already linked from the hackathon page cover slices of this:
Those remain useful. This issue is the map of the whole intersection so a plan (and any code) can be discussed against a shared picture of what exists.
Why there are two generators
Grails currently has two independent application generators that produce overlapping, but not identical, projects:
YAML + Groovy skeletons, Eclipse Aether profile JARs, Ant copy/filter
grails-profiles/, consumed by grails-shell-cli/
Forge
grails -t forge create-app, start.grails.org, latest.grails.org (and the other version slots), curl ZIP
Micronaut Launch-style Java features + Rocker templates
grails-forge/
They are not merely two UIs over one engine. They do not share templates, they do not share a feature model, they do not share a command runtime, and they do not produce the same build.gradle. Forge-generated apps still declare a profile dependency so that the shell CLI can keep working after generation. Profiles still exist as published Maven artifacts (org.apache.grails.profiles:{web,plugin,…}) that both generators and the shell CLI consume for different reasons.
The grails binary users install is a third piece: DelegatingShellApplication in grails-forge/grails-cli, which dispatches to either Forge CLI or Shell CLI.
There is also a fourth command path after Grails 8: Gradle ApplicationCommand / companion -cli artifacts / the grailsCli configuration (see the user guide "CLI Commands Move to Companion -cli Artifacts"). That path is how dbm-*, generate-*, url-mappings-report, schema-export and similar commands are discovered at build time. It is neither a profile YAML command nor a Forge Picocli command.
Historical context
Grails 1–2: Gant scripts in the distribution
Before Grails 3, grails create-app and artefact commands were Gant scripts shipped inside the Grails distribution. There was one generator and one CLI. Plugins could add scripts. There were no profiles and no Forge.
Grails 3.0 (2015): Profiles + Shell CLI
Grails 3 rebuilt the CLI around profiles: versioned Maven JARs containing profile.yml, a project skeleton, optional features, YAML/Groovy commands, and artefact templates. This is documented as a deliberate break from earlier Grails CLIs (grails-doc still opens the command-line chapter with "Grails 3.0's command line system differs greatly from previous versions").
Profiles solved several Grails 3 problems at once:
Application types as reusable, inheritable JARs (web, rest-api, plugin, …) rather than one hard-coded skeleton.
Commands after creation (create-controller, run-app, generate-all, …) living in the same JAR as the skeleton, resolved from the app's profile dependency.
Third-party / company profiles published to Maven, resolved with Eclipse Aether before Gradle exists (because create-app has no Gradle yet).
Gradle Tooling API for run-app / test-app / compile so the CLI could drive the new Gradle build.
The profile compiler and publisher are Gradle plugins (org.apache.grails.gradle.grails-profile, GrailsProfilePublishGradlePlugin). The IntelliJ Grails plugin historically talked to this shell CLI.
CreateAppCommand is annotated @since 3.0. AbstractProfile is @since 3.1. create-profile arrived so organisations could mint their own profiles.
Grails 6 (circa 2022): Forge, from Micronaut Launch
Grails Forge is a fork/adaptation of Micronaut Launch (the Micronaut equivalent of Spring Initializr). Feature classes are @since 6.0.0. The architecture is:
Java Feature beans, selected and validated, then apply(GeneratorContext)
Rocker templates for generated files
Picocli CLI (grails-forge-cli)
HTTP API (grails-forge-api + Netty) serving start.grails.org
Optional analytics
A React UI in a separate repo (apache/grails-forge-ui)
Forge was introduced as the modern generator: feature combinatorics, a website, curl, JDK/servlet/GORM options, preview/diff. The Micronaut origin is still visible: Language has only GROOVY, BuildTool has only GRADLE, TestFramework has only SPOCK, and Category still lists BPM, IoT, serverless, distributed tracing, service discovery, etc.
The "gradual transition" (Grails 6–7)
#14081 records the intent at the time: Forge is "the modern way to generate Grails applications"; profile generation via shell CLI "was brought back to support a more gradual transition"; "that other 90%" of the shell CLI (run, test, Gradle, interactive mode, IntelliJ, ApplicationCommands) "will continue to be important."
Profiles were then a "redundant burden" because they use "totally different technology" from Forge, and keeping skeletons/dependencies aligned is manual.
#14699 (closed, Grails 7.0.0-M5) was the work to make 7.0.x profiles generate valid Grails 7 apps that match Forge defaults "as much as possible." Drift did not stop there: #14933 and #14944 are still open for remaining dependency mismatches.
Apache monorepo (Grails 7)
grails-forge and grails-profiles used to be separate GitHub repositories. They now live in apache/grails-core (grails-forge/ is its own Gradle build; grails-profiles/ is part of the root build). The hackathon page still points at the old standalone repos; the code to work on is this monorepo.
Grails 8: more CLI surface, not less
Grails 8 added:
Hibernate 7 as a Forge --data option (gorm-hibernate7), while default GORM is still Hibernate 5 (GormImpl.DEFAULT_OPTION = HIBERNATE5) and profiles still default the hibernate5 feature.
Companion -cli artifacts and grailsCli auto-provisioning so plugin commands are not on the runtime classpath.
DelegatingShellApplication still defaults GRAILS_PREFERRED_SHELL to shell, not forge.
The user guide currently documents both CLIs in the same chapter, sometimes contradicting itself about which create-app is preferred.
Forge Picocli commands are Micronaut beans and "can't be nested", so the wrapper cannot expose a single Picocli tree with autocomplete across both CLIs (comment on DelegatingShellApplication).
-t / --type is consumed by the wrapper and stripped before the chosen CLI sees the rest. Forge's own -t is --test (test framework). Shell does not use -t. This is easy to confuse.
Default with no -t is shell, even though parts of the user guide call Forge "the preferred method for initiating new Grails projects."
Inside a generated app, ./grails is the wrapper again. Docs tell you to generate with Forge then switch to shell for run/test/deploy.
Three published-ish CLIs therefore exist in source:
Published as org.apache.grails.profiles:<name> JARs. Layout inside the JAR (META-INF/grails-profile/):
profile.yml
commands/ # YAML and/or Groovy CLI commands
features/<name>/ # feature.yml + optional skeleton
skeleton/ # files copied into the new project
templates/ # artefact templates for create-*/generate-*
Compiled by GrailsProfileGradlePlugin / ProfileCompilerTask.
Inheritance tree (current monorepo)
base
├── web
│ └── web-plugin (also extends plugin)
├── plugin
│ └── web-plugin
│ └── rest-api-plugin (also extends rest-api)
├── rest-api
│ └── rest-api-plugin
└── (profile) # meta-profile for creating new profiles; does not extend base in profile.yml
Inheritance is Gradle profileRuntimeApi, not profile.ymlextends: (that key exists in AbstractProfile but the shipping profiles use Gradle). Order of profileRuntimeApi declarations controls skeleton copy order and command override order.
web-plugin additionally sets build.merge: [base, web, plugin] so parent build.gradle fragments are concatenated rather than first-wins.
profile.yml schema (what it can express)
Documented in grails-doc / profileStructure.adoc, implemented in AbstractProfile + CreateAppCommand:
commands map (class name → groovy/yml) — used less than directory-scanned commands
Generation algorithm (CreateAppCommand.handle)
Resolve profile name (--profile or default: web / plugin / web-plugin / profile depending on command).
Resolve the profile JAR via MavenProfileRepository + Eclipse Aether (not Gradle). Repos from ~/.grails/settings.groovygrails.profiles.repositories, plus ~/.m2/settings.xml for proxies/auth. SNAPSHOT Grails versions add ASF snapshot and Sitemesh snapshot repos into the generated build.
Evaluate features: requested --features ∩ profile features, plus required features. If none requested, defaults + required. Unknown feature names warn with a 2-character prefix "possible solutions" heuristic; they do not fail the command.
Parse app name: com.example.myapp → group com.example, app myapp; bare myapp → synthesized package from hyphenated tokens. --inplace uses the current directory name.
Refuse non-empty target directories.
For each profile in the inheritance chain, unzip if needed, copy skeleton/ with Ant:
Token filter on file contents and paths (@grails.codegen.defaultPackage.path@, etc.).
Binary extensions skipped by the text filter.
application.yml documents concatenated with --- separators (not a deep YAML merge).
build.gradle / gradle.properties concatenated when merge is enabled.
Copy each selected feature's skeleton/ the same way; append feature application.yml / build.gradle / gradle.properties.
replaceBuildTokens: inject @dependencies@, @buildPlugins@, @repositories@, @buildDependencies@, @buildRepositories@, plus the variable map. Always addprofile "org.apache.grails.profiles:<name>" as a Gradle profile scope dependency.
chmod executable patterns (gradlew*, grailsw*).
Print instructions if any.
Skeleton tokens (undocumented in the user guide — #11496)
Merges base+web+plugin; excludes WAR and grails-web plugin; excludes many skeleton files (assets, i18n, spring, UrlMappings, BootStrap); excludes war command
grails-plugin plugin; extra modules (codecs, async, datasource); Micronaut HTTP client on tests; excludes controllers/views/i18n skeleton
profile
—
—
Skeleton for authoring a new profile; commands: create-command, create-creator-command, create-generator-command, create-feature, create-gradle-command, create-template, install
Base profile optional features (inherited by children): hibernate5, mongodb, rx-mongodb, neo4j, gsp, json-views, markup-views, asset-pipeline, less-asset-pipeline, geb2, events.
Profile feature quirks
geb2: feature.yml has empty dependencies / plugins. Geb comes from the web profile's integrationTestImplementation testFixtures("org.apache.grails:grails-geb"), not from the feature. The name is historical (Geb 2).
hibernate5: still the default GORM for web/rest-api/rest-api-plugin. Adds H2 + HikariCP. No hibernate7 profile feature exists.
rx-mongodb: coordinates org.grails.plugins:rx-mongodb and org.apache.grails.async:grails-async-rxjava — pre-Apache / possibly stale.
security on rest-api: hard-pins grails-spring-security-rest:7.0.0-SNAPSHOT.
Features are not exclusive. Profiles have no equivalent of Forge's OneOfFeature / servlet/GORM enums. Selecting mongodb does not clearly turn off hibernate5 unless the user omits the default by passing an explicit --features list (which replaces defaults, then re-adds required).
Passing --featuresdrops unspecified defaults. grails create-app x --features mongodb does not keep hibernate5/geb2 unless also listed; required gsp/asset-pipeline still apply.
Profile commands (the "other 90%")
Profiles are not only a generator. After create-app, grails-shell-cli loads commands from:
profile (the meta-profile) is a generator for generators.
Interactive JLine shell, completers (including domain-class completers), --non-interactive, and list-profiles / profile-info (only outside a project — documented) are all shell/profile concerns.
list-profiles / profile-info / custom profile GAV (--profile=com.mycompany.grails.profiles:myprofile:1.0.0) and ~/.grails/settings.groovy groupId/version defaults have no Forge equivalent.
How Forge generates an application
Modules
Module
Role
grails-forge-core
Features, options, ProjectGenerator, Rocker templates, Gradle model
grails-forge-cli
Picocli commands, interactive shell, codegen
grails-forge-api
HTTP: create ZIP, preview, diff, select-options, features, version
grails-forge-web-netty
Runnable API server
grails-forge-analytics-postgres
Optional analytics (not deployed on current AWS slots)
Resolve named features; unknown feature names fail (IllegalArgumentException).
Apply every DefaultFeature whose shouldApply(type, options, selected) is true (always-on features: base, grails-profiles, Gradle, logback, yaml, grails-application, … plus type-specific ones).
validatePreProcessing (e.g. OneOfFeature groups: one servlet, one GORM, one logging config, one primary security).
processSelectedFeatures — features may add/exclude others (getDependentFeatures is descriptive metadata for UIs).
validatePostProcessing.
Write grails-forge-cli.yml listing the resolved feature names and options (legacy grails-cli.yml still read; profile: key marks v1 format and triggers update-cli-config warning).
Render all templates through TemplateRenderer to filesystem or ZIP.
Forge is offline. It does not invoke Gradle, does not start the app, and does not resolve profile JARs at generation time. Versions come from bundled grails-versions.properties (see #14858: SNAPSHOT Forge still needs a rebuild to pick up BOM bumps).
Options (first-class, not features)
Option
Values
Default
Profile equivalent
--jdk / --java-version
21, 25, 26
21
none (wrapper/Gradle whatever the skeleton has)
-s / --servlet
tomcat, jetty, undertow, none
tomcat
web/rest-api hardcode Tomcat starter
-d / --data (-g/--gorm legacy)
hibernate5, hibernate7, mongodb (legacy hibernate → hibernate5). Enum also has neo4j
hibernate5
--features hibernate5|mongodb|neo4j
-r / --reloading
devtools, jrebel, none
devtools
base always adds spring-boot-devtools
--list-features
—
—
no equivalent (profile-info lists profile features)
-f / --features
comma list, additive on top of defaults
—
--featuresreplaces defaults
-i / --inplace
current dir
—
same flag; Forge historically errored if NAME omitted (#14872)
Docs still mention -t / --test with junit, spock. TestFramework enum currently has only SPOCK. User-guide feature lists are stale (still mention assertj, hamcrest, logbackGroovy, grails-gsp as a flag, embedded-mongodb, micronaut-inject-groovy).
Hibernate 7 is not just a dependency swap: GrailsBase switches the BOM to grails-hibernate7-bom or grails-hibernate7-micronaut-bom so Hibernate 7 and the default Hibernate 5 BOM do not fight (user guide; issue #15942). Profiles cannot do this — they always emit grails-bom.
The hidden grails-profiles feature
GrailsProfiles is a DefaultFeature with shouldApply = true, isVisible = false. Every Forge app:
sets grails.profile to the application type with underscores turned into hyphens (web_plugin → web-plugin)
sets grails.codegen.defaultPackage
adds profile "org.apache.grails.profiles:<type>"
So Forge generation still requires published profile JARs at app runtime if the user wants shell-CLI profile commands. Forge did not replace profiles as a runtime command pack; it replaced them as a skeleton engine and still emits the old contract.
There is no Forge application type for rest-api-plugin, so that profile JAR is never selected by this feature.
Codegen (registered only if grails-forge-cli.yml / legacy grails-cli.yml is present): create-controller, create-service, create-domain-class, create-taglib, create-interceptor, create-job (Quartz; applies() gated).
These are Rocker templates, not profile YAML. They do not create the extra files some profile commands do (e.g. web create-controller also mkdirs grails-app/views/<name>). They do not include rest-api generate-all / create-restful-controller / create-functional-test. They do not invoke Gradle.
Forge interactive mode is its own Picocli loop (InteractiveShell), not JLine profile completion.
API slots on AWS Elastic Beanstalk: latest, snapshot, next, next-snapshot, prev, prev-snapshot, older — seven Forge versions behind one ALB
GET /{name}.zip (and typed variants) for curl
Preview and diff endpoints
OpenAPI / Swagger / RapiDoc
GitHub create/OAuth exists in the codebase but is not deployed (README)
Analytics module exists but is not deployed
Profiles have no website, no preview, no diff, no curl ZIP, no version slots.
The third command path: Gradle ApplicationCommand / -cli artifacts
Independent of both generators:
Plugins publish a companion *-cli JAR (Grails-Cli-Artifact manifest, META-INF/grails-cli.factories).
The Grails Gradle plugin puts those on grailsCli (not runtimeClasspath).
Shell CLI's ApplicationContextCommandFactory adapts them into grails <command> via Gradle Tooling API.
Gradle also exposes tasks directly (dbmUpdate, urlMappingsReport, …).
Grails 8 moved this contract to org.apache.grails.core.cli.*. Legacy grails.dev.commands.* can still be bridged with legacyCommandSupport.
This is why "one CLI" is a larger question than "Forge vs profiles": even if generation is unified, lifecycle and plugin commands currently span shell CLI, Gradle tasks, and -cli artifacts. Forge CLI does not participate in this path at all.
Forge always emits profile "org.apache.grails.profiles:<type>" even though it did not use that profile to generate files.
Profiles concatenate application.yml documents; Forge builds a single config model then writes YAML (or properties).
Token/@dependencies@ skeletons vs fully generated Gradle from a Java model — whitespace, plugin application style, platform vs enforcedPlatform, buildSrc vs buildscript classpath all differ.
Forge can emit grails-hibernate7-bom; profiles cannot.
Same command names therefore do different things depending on -t forge vs default shell, and depending on whether you are inside a project.
Documentation quirks
commandLine.adoc is two eras concatenated: Grails 3 profile lookup (PROJECT_HOME/src/main/scripts, profile commands/) then "The Grails Forge New Command-Line Interface". creatingAnApplication.adoc shows both CLIs. creatingProject.adoc lists Forge flags whose values are out of date. Profile guide still shows org.grails.grails-core plugin ids and hibernate feature names. Hackathon getting-started still clones standalone grails-forge / grails-profiles repos.
Other open issues that are slices of this
Unify and Simplify CLI Command Architecture #15301 — CLI command architecture spread across libraries (design-required; comments already ask whether to enhance Forge vs keep shell). Milestone 9.0.0-M1.
Open questions (for the hackathon — not proposals)
These are the decisions the event asked for a plan on. Listing them is not choosing an answer.
Generator
Is the long-term generator the Forge feature/Rocker engine, the profile skeleton/YAML engine, a shared template store both call, or something else?
If one engine remains, what happens to the other's unique types (rest-api-plugin, create-profile, third-party published profiles)?
Forge apps still depend on profile JARs for shell commands. Is that a bridge to remove, a permanent split (generate with A, command-pack with B), or the wrong layering?
How should Hibernate 7 / Micronaut BOM switching work if profiles remain a generator?
How should exclusive options (servlet, GORM, JDK) work if profiles remain a generator?
What is the compatibility story for existing ~/.grails/settings.groovy profile repos and in-house company profiles?
CLI binary and command structure
What is the user-facing command set of grails going forward? One tree, or generate-with-Forge / operate-with-shell forever?
What should the default be when GRAILS_PREFERRED_SHELL is unset? Docs and code disagree today.
-t is wrapper-type vs Forge --test. What should flags mean?
Which artefact commands belong in the CLI vs Gradle vs -cli artifacts? create-controller exists in both CLIs and is not the same implementation.
Where do run-app, interactive mode, and IntelliJ integration live if shell is no longer the generator?
Can Picocli (Forge) and JLine/profile commands (shell) share completion and help, given the Micronaut-bean nesting constraint on the wrapper?
What is the story for ApplicationCommand / Grails 8 -cli artifacts relative to profile YAML commands and Forge codegen?
Which features on each side are load-bearing vs leftover (profile rx-mongodb / geb2 / SNAPSHOT security pin; Forge Category leftovers, Language/BuildTool/TestFramework enums)?
Docs and distribution
One user-guide story for create-app (the chapter currently tells both stories).
Website / curl / version slots if Forge is not the generator — or if it is the only generator, what happens to list-profiles docs.
SDKMAN contents, native images, and the project ./grails wrapper.
This issue is the Community over Code Glasgow 2026 working document for one Grails application generator and one agreed CLI command structure. It inventories how the two generators and the several CLIs actually work today. It does not prescribe combining them, deprecating one, or mixing them.
The three issues already linked from the hackathon page cover slices of this:
plugin/web-pluginThose remain useful. This issue is the map of the whole intersection so a plan (and any code) can be discussed against a shared picture of what exists.
Why there are two generators
Grails currently has two independent application generators that produce overlapping, but not identical, projects:
grails create-app(shell CLI default),create-plugin,create-web-plugin,create-profilegrails-profiles/, consumed bygrails-shell-cli/grails -t forge create-app,start.grails.org,latest.grails.org(and the other version slots), curl ZIPgrails-forge/They are not merely two UIs over one engine. They do not share templates, they do not share a feature model, they do not share a command runtime, and they do not produce the same
build.gradle. Forge-generated apps still declare a profile dependency so that the shell CLI can keep working after generation. Profiles still exist as published Maven artifacts (org.apache.grails.profiles:{web,plugin,…}) that both generators and the shell CLI consume for different reasons.The
grailsbinary users install is a third piece:DelegatingShellApplicationingrails-forge/grails-cli, which dispatches to either Forge CLI or Shell CLI.There is also a fourth command path after Grails 8: Gradle
ApplicationCommand/ companion-cliartifacts / thegrailsCliconfiguration (see the user guide "CLI Commands Move to Companion-cliArtifacts"). That path is howdbm-*,generate-*,url-mappings-report,schema-exportand similar commands are discovered at build time. It is neither a profile YAML command nor a Forge Picocli command.Historical context
Grails 1–2: Gant scripts in the distribution
Before Grails 3,
grails create-appand artefact commands were Gant scripts shipped inside the Grails distribution. There was one generator and one CLI. Plugins could add scripts. There were no profiles and no Forge.Grails 3.0 (2015): Profiles + Shell CLI
Grails 3 rebuilt the CLI around profiles: versioned Maven JARs containing
profile.yml, a project skeleton, optional features, YAML/Groovy commands, and artefact templates. This is documented as a deliberate break from earlier Grails CLIs (grails-docstill opens the command-line chapter with "Grails 3.0's command line system differs greatly from previous versions").Profiles solved several Grails 3 problems at once:
web,rest-api,plugin, …) rather than one hard-coded skeleton.create-controller,run-app,generate-all, …) living in the same JAR as the skeleton, resolved from the app'sprofiledependency.create-apphas no Gradle yet).run-app/test-app/compileso the CLI could drive the new Gradle build.The profile compiler and publisher are Gradle plugins (
org.apache.grails.gradle.grails-profile,GrailsProfilePublishGradlePlugin). The IntelliJ Grails plugin historically talked to this shell CLI.CreateAppCommandis annotated@since 3.0.AbstractProfileis@since 3.1.create-profilearrived so organisations could mint their own profiles.Grails 6 (circa 2022): Forge, from Micronaut Launch
Grails Forge is a fork/adaptation of Micronaut Launch (the Micronaut equivalent of Spring Initializr). Feature classes are
@since 6.0.0. The architecture is:Featurebeans, selected and validated, thenapply(GeneratorContext)grails-forge-cli)grails-forge-api+ Netty) servingstart.grails.orgapache/grails-forge-ui)Forge was introduced as the modern generator: feature combinatorics, a website, curl, JDK/servlet/GORM options, preview/diff. The Micronaut origin is still visible:
Languagehas onlyGROOVY,BuildToolhas onlyGRADLE,TestFrameworkhas onlySPOCK, andCategorystill lists BPM, IoT, serverless, distributed tracing, service discovery, etc.The "gradual transition" (Grails 6–7)
#14081 records the intent at the time: Forge is "the modern way to generate Grails applications"; profile generation via shell CLI "was brought back to support a more gradual transition"; "that other 90%" of the shell CLI (run, test, Gradle, interactive mode, IntelliJ,
ApplicationCommands) "will continue to be important."Profiles were then a "redundant burden" because they use "totally different technology" from Forge, and keeping skeletons/dependencies aligned is manual.
#14699 (closed, Grails 7.0.0-M5) was the work to make 7.0.x profiles generate valid Grails 7 apps that match Forge defaults "as much as possible." Drift did not stop there: #14933 and #14944 are still open for remaining dependency mismatches.
Apache monorepo (Grails 7)
grails-forgeandgrails-profilesused to be separate GitHub repositories. They now live inapache/grails-core(grails-forge/is its own Gradle build;grails-profiles/is part of the root build). The hackathon page still points at the old standalone repos; the code to work on is this monorepo.Grails 8: more CLI surface, not less
Grails 8 added:
--dataoption (gorm-hibernate7), while default GORM is still Hibernate 5 (GormImpl.DEFAULT_OPTION = HIBERNATE5) and profiles still default thehibernate5feature.-cliartifacts andgrailsCliauto-provisioning so plugin commands are not on the runtime classpath.DelegatingShellApplicationstill defaultsGRAILS_PREFERRED_SHELLtoshell, notforge.The user guide currently documents both CLIs in the same chapter, sometimes contradicting itself about which
create-appis preferred.Current topology (what a user actually runs)
Quirks of the dispatcher:
DelegatingShellApplication).-t/--typeis consumed by the wrapper and stripped before the chosen CLI sees the rest. Forge's own-tis--test(test framework). Shell does not use-t. This is easy to confuse.-tis shell, even though parts of the user guide call Forge "the preferred method for initiating new Grails projects."./grailsis the wrapper again. Docs tell you to generate with Forge then switch to shell for run/test/deploy.Three published-ish CLIs therefore exist in source:
grails-cli(shadow)DelegatingShellApplicationgrailsbinarygrails-forge-cliorg.grails.forge.cli.Applicationgrails-shell-cliorg.grails.cli.GrailsCliHow Profiles generate an application
Profile artifacts
Published as
org.apache.grails.profiles:<name>JARs. Layout inside the JAR (META-INF/grails-profile/):Compiled by
GrailsProfileGradlePlugin/ProfileCompilerTask.Inheritance tree (current monorepo)
Inheritance is Gradle
profileRuntimeApi, notprofile.ymlextends:(that key exists inAbstractProfilebut the shipping profiles use Gradle). Order ofprofileRuntimeApideclarations controls skeleton copy order and command override order.web-pluginadditionally setsbuild.merge: [base, web, plugin]so parentbuild.gradlefragments are concatenated rather than first-wins.profile.ymlschema (what it can express)Documented in
grails-doc/profileStructure.adoc, implemented inAbstractProfile+CreateAppCommand:description,instructions(printed after create)repositories,build.repositories,build.plugins,build.excludesdependencies(scope + coords + optional transitive excludes;excludesscope strips parent deps)features.defaults,features.requiredskeleton.excludes,skeleton.parent.target(multi-project),skeleton.binaryExtensions,skeleton.executable(Ant patterns, chmod)command.excludescommandsmap (class name → groovy/yml) — used less than directory-scanned commandsGeneration algorithm (
CreateAppCommand.handle)--profileor default:web/plugin/web-plugin/profiledepending on command).~/.grails/settings.groovygrails.profiles.repositories, plus~/.m2/settings.xmlfor proxies/auth. SNAPSHOT Grails versions add ASF snapshot and Sitemesh snapshot repos into the generated build.--features∩ profile features, plus required features. If none requested, defaults + required. Unknown feature names warn with a 2-character prefix "possible solutions" heuristic; they do not fail the command.com.example.myapp→ groupcom.example, appmyapp; baremyapp→ synthesized package from hyphenated tokens.--inplaceuses the current directory name.skeleton/with Ant:@grails.codegen.defaultPackage.path@, etc.).application.ymldocuments concatenated with---separators (not a deep YAML merge).build.gradle/gradle.propertiesconcatenated when merge is enabled.skeleton/the same way; append featureapplication.yml/build.gradle/gradle.properties.replaceBuildTokens: inject@dependencies@,@buildPlugins@,@repositories@,@buildDependencies@,@buildRepositories@, plus the variable map. Always addprofile "org.apache.grails.profiles:<name>"as a Gradleprofilescope dependency.gradlew*,grailsw*).Skeleton tokens (undocumented in the user guide — #11496)
Set in
CreateAppCommand.initializeVariables:@grails.codegen.defaultPackage@@grails.codegen.defaultPackage.path@@grails.codegen.projectClassName@Myapp@grails.codegen.projectNaturalName@Myappnatural@grails.codegen.projectName@@grails.profile@@grails.version@@grails.app.name@/@grails.app.group@@APPNAME@@gorm.version@,@groovy.version@,@grails-gradle-plugins.version@Command templates later use
@artifact.package.path@,@artifact.name@,@artifact.propertyName@.Shipping profiles and their features
eventshibernate5,geb2gsp,asset-pipelinemaven-publish,asset-pipeline-pluginasset-pipeline-plugingspwarcommandhibernate5json-viewssecurity(Spring Security REST, pinned7.0.0-SNAPSHOT)hibernate5json-viewscreate-command,create-creator-command,create-generator-command,create-feature,create-gradle-command,create-template,installBase profile optional features (inherited by children):
hibernate5,mongodb,rx-mongodb,neo4j,gsp,json-views,markup-views,asset-pipeline,less-asset-pipeline,geb2,events.Profile feature quirks
geb2:feature.ymlhas emptydependencies/plugins. Geb comes from the web profile'sintegrationTestImplementation testFixtures("org.apache.grails:grails-geb"), not from the feature. The name is historical (Geb 2).hibernate5: still the default GORM for web/rest-api/rest-api-plugin. Adds H2 + HikariCP. Nohibernate7profile feature exists.rx-mongodb: coordinatesorg.grails.plugins:rx-mongodbandorg.apache.grails.async:grails-async-rxjava— pre-Apache / possibly stale.neo4j:org.grails.plugins:neo4j— same vintage.json-views: also pullsgrails-data-mongodb-gson-templateseven for Hibernate apps (views-json-templates dependency may not be needed by default with the views-json feature #14868 is the Forge-side cousin of this).securityon rest-api: hard-pinsgrails-spring-security-rest:7.0.0-SNAPSHOT.OneOfFeature/ servlet/GORM enums. Selectingmongodbdoes not clearly turn offhibernate5unless the user omits the default by passing an explicit--featureslist (which replaces defaults, then re-adds required).--featuresdrops unspecified defaults.grails create-app x --features mongodbdoes not keephibernate5/geb2unless also listed; requiredgsp/asset-pipelinestill apply.Profile commands (the "other 90%")
Profiles are not only a generator. After
create-app,grails-shell-cliloads commands from:ServiceLoadercommands (META-INF/services/org.grails.cli.profile.Command):create-app,create-plugin,create-web-plugin,create-profile,open,help,list-profiles,profile-info, Gradle command, RunCommand.CommandFactoryimplementations: YAML files, Groovy scripts, ServiceLoader commands, ApplicationContextCommandFactory (adapts GradleApplicationCommands /-cliartifacts to the shell).commands/*.ymlandcommands/*.groovy.src/main/scripts/*.groovy.YAML steps:
render,mkdir,execute,gradle.Groovy scripts extend
GroovyScriptCommand(templates, flags, completers, synonyms).base commands include lifecycle that invoke Gradle via the Tooling API:
run-app,stop-app,test-app,compile,clean,assemble,package,console,shell,stats,dependency-report,list-plugins,plugin-info,bug-report,run-script,run-command,add-property, plus artefactcreate-domain-class,create-unit-test,create-script,create-command.web adds
create-controller,create-service,create-taglib,create-interceptor,create-integration-test.rest-api adds REST variants (
create-restful-controller,create-domain-resource,create-functional-test) and Groovygenerate-all/generate-controller/generate-views/generate-unit-test/generate-functional-test.plugin adds
publish-plugin,package-plugin,install.profile (the meta-profile) is a generator for generators.
Interactive JLine shell, completers (including domain-class completers),
--non-interactive, andlist-profiles/profile-info(only outside a project — documented) are all shell/profile concerns.list-profiles/profile-info/ custom profile GAV (--profile=com.mycompany.grails.profiles:myprofile:1.0.0) and~/.grails/settings.groovygroupId/version defaults have no Forge equivalent.How Forge generates an application
Modules
grails-forge-coreProjectGenerator, Rocker templates, Gradle modelgrails-forge-cligrails-forge-apigrails-forge-web-nettygrails-forge-analytics-postgresgrails-cli/grails-cli-shadowtest-coreApplication types
ApplicationTypeenum:WEB,REST_API,WEB_PLUGIN,PLUGIN. DefaultWEB.No
REST_API_PLUGIN. NoPROFILE. Tracked as #14932 (blocked on #14944).Each type has its own
AvailableFeaturesbean (WebAvailableFeatures, …) so the selectable set differs.Generation algorithm (
DefaultProjectGenerator+ContextFactory)NameUtils.parse) — package + app from dotted name;--inplaceuses current dir.Options(reloading, GORM, servlet, JDK, OS). Nulls become defaults: DevTools, Hibernate 5, Tomcat, JDK 21.IllegalArgumentException).DefaultFeaturewhoseshouldApply(type, options, selected)is true (always-on features:base,grails-profiles, Gradle, logback, yaml, grails-application, … plus type-specific ones).validatePreProcessing(e.g.OneOfFeaturegroups: one servlet, one GORM, one logging config, one primary security).processSelectedFeatures— features may add/exclude others (getDependentFeaturesis descriptive metadata for UIs).validatePostProcessing.grails-forge-cli.ymllisting the resolved feature names and options (legacygrails-cli.ymlstill read;profile:key marks v1 format and triggersupdate-cli-configwarning).applyeach feature: add Rocker/URL templates, Gradle deps/plugins, configuration keys, binary resources.TemplateRendererto filesystem or ZIP.Forge is offline. It does not invoke Gradle, does not start the app, and does not resolve profile JARs at generation time. Versions come from bundled
grails-versions.properties(see #14858: SNAPSHOT Forge still needs a rebuild to pick up BOM bumps).Options (first-class, not features)
--jdk/--java-version-s/--servlet-d/--data(-g/--gormlegacy)hibernate→ hibernate5). Enum also has neo4j--features hibernate5|mongodb|neo4j-r/--reloading--list-features-f/--features--featuresreplaces defaults-i/--inplaceDocs still mention
-t/--testwithjunit, spock.TestFrameworkenum currently has onlySPOCK. User-guide feature lists are stale (still mentionassertj,hamcrest,logbackGroovy,grails-gspas a flag,embedded-mongodb,micronaut-inject-groovy).Forge feature inventory (names)
Always-on / often invisible (
DefaultFeature, manyisVisible() == false):base,gradle,gradle-settings-file,gradle-build-src,yaml,logback,logback-config(opt-in XML),grails-application,grails-dependencies,grails-gradle-plugin,grails-url-mappings,grails-web,grails-profiles,grails-wrapper,app-name,readme,spring-boot-autoconfigure,spring-boot-starter,spring-resources,spock,grails-gorm-testing-support, …GORM / data:
gorm-hibernate5,gorm-hibernate7,gorm-mongodb,gorm-neo4j,gorm-async,gorm-graphql,h2,mysql,postgres,sqlserver,mongo-sync,testcontainers,mongodb-testing-support,database-migration,hibernate-validator.Views:
gsp(docs sometimes saygrails-gsp),grails-layout,sitemesh3,scaffolding,views-json,views-markup,views-json-testing-support.Servlet:
spring-boot-starter-tomcat,spring-boot-starter-jetty,grails-undertow.Security:
grails-spring-security,grails-spring-security-ui,spring-boot-starter-security.Reloading:
spring-boot-devtools,jrebel.Other selectable:
asset-pipeline-grails,cache,cache-ehcache,asciidoctor,github-workflow-java-ci,micronaut-http-client,grails-micronaut,grails-quartz,grails-web-console,shade,properties,mockito,geb-with-testcontainers,geb-with-webdriver-binaries,spring-boot-virtual-threads.Hibernate 7 is not just a dependency swap:
GrailsBaseswitches the BOM tograils-hibernate7-bomorgrails-hibernate7-micronaut-bomso Hibernate 7 and the default Hibernate 5 BOM do not fight (user guide; issue #15942). Profiles cannot do this — they always emitgrails-bom.The hidden
grails-profilesfeatureGrailsProfilesis aDefaultFeaturewithshouldApply = true,isVisible = false. Every Forge app:grails.profileto the application type with underscores turned into hyphens (web_plugin→web-plugin)grails.codegen.defaultPackageprofile "org.apache.grails.profiles:<type>"So Forge generation still requires published profile JARs at app runtime if the user wants shell-CLI profile commands. Forge did not replace profiles as a runtime command pack; it replaced them as a skeleton engine and still emits the old contract.
There is no Forge application type for
rest-api-plugin, so that profile JAR is never selected by this feature.Forge CLI commands
Create (always registered):
create-app,create-webapp(alias of web),create-restapi,create-plugin,create-web-plugin.Codegen (registered only if
grails-forge-cli.yml/ legacygrails-cli.ymlis present):create-controller,create-service,create-domain-class,create-taglib,create-interceptor,create-job(Quartz;applies()gated).These are Rocker templates, not profile YAML. They do not create the extra files some profile commands do (e.g. web
create-controlleralsomkdirsgrails-app/views/<name>). They do not include rest-apigenerate-all/create-restful-controller/create-functional-test. They do not invoke Gradle.Forge interactive mode is its own Picocli loop (
InteractiveShell), not JLine profile completion.Forge HTTP / website
https://start.grails.org(React,apache/grails-forge-ui)latest,snapshot,next,next-snapshot,prev,prev-snapshot,older— seven Forge versions behind one ALBGET /{name}.zip(and typed variants) for curlProfiles have no website, no preview, no diff, no curl ZIP, no version slots.
The third command path: Gradle
ApplicationCommand/-cliartifactsIndependent of both generators:
*-cliJAR (Grails-Cli-Artifactmanifest,META-INF/grails-cli.factories).grailsCli(notruntimeClasspath).ApplicationContextCommandFactoryadapts them intograils <command>via Gradle Tooling API.dbmUpdate,urlMappingsReport, …).Grails 8 moved this contract to
org.apache.grails.core.cli.*. Legacygrails.dev.commands.*can still be bridged withlegacyCommandSupport.This is why "one CLI" is a larger question than "Forge vs profiles": even if generation is unified, lifecycle and plugin commands currently span shell CLI, Gradle tasks, and
-cliartifacts. Forge CLI does not participate in this path at all.Compare and contrast
Surfaces
grails create-app)grails -t forge …)start.grails.orgcreate-profilemeta-generatorrun-app,test-app, …)grailsbinaryApplication types
ApplicationTypeWEBREST_APIPLUGINWEB_PLUGINbasefeature, different meaningFeature model
feature.yml+ skeleton filesFeatureclass + Rocker/URL templates--featuresreplaces defaults--featuresadditive;OneOfFeaturevalidatorsgrails-micronaut/ hibernate7-micronaut BOMgithub-workflow-java-cigeb2(mostly empty)geb-with-testcontainers,geb-with-webdriver-binaries,testcontainerssecurityfeature (REST, SNAPSHOT pin); no web-app equivalentgrails-spring-security, UI, Boot startergrails-quartz+create-jobGenerated build / config drift (known)
#14944 / #14933 document concrete coordinate differences. Examples:
grails-layout, H2, Hikari, jansi that the web-plugin profile may not.grails-async; Forge rest-api may not (Update Profile Dependencies to more closely match Grails Forge Defaults #14933).profile "org.apache.grails.profiles:<type>"even though it did not use that profile to generate files.application.ymldocuments; Forge builds a single config model then writes YAML (or properties).@dependencies@skeletons vs fully generated Gradle from a Java model — whitespace, plugin application style,platformvsenforcedPlatform,buildSrcvs buildscript classpath all differ.grails-hibernate7-bom; profiles cannot.Post-create artefact commands
-cliis a separate path)AddPropertyCommandSame command names therefore do different things depending on
-t forgevs default shell, and depending on whether you are inside a project.Documentation quirks
commandLine.adocis two eras concatenated: Grails 3 profile lookup (PROJECT_HOME/src/main/scripts, profilecommands/) then "The Grails Forge New Command-Line Interface".creatingAnApplication.adocshows both CLIs.creatingProject.adoclists Forge flags whose values are out of date. Profile guide still showsorg.grails.grails-coreplugin ids andhibernatefeature names. Hackathon getting-started still clones standalonegrails-forge/grails-profilesrepos.Other open issues that are slices of this
design-required; comments already ask whether to enhance Forge vs keep shell). Milestone 9.0.0-M1.--inplacewithout NAME.views-jsonpulling gson-templates.views-markup.Closed but useful history: #14699 (make 7.x profiles generate valid apps).
Open questions (for the hackathon — not proposals)
These are the decisions the event asked for a plan on. Listing them is not choosing an answer.
Generator
rest-api-plugin,create-profile, third-party published profiles)?~/.grails/settings.groovyprofile repos and in-house company profiles?CLI binary and command structure
grailsgoing forward? One tree, or generate-with-Forge / operate-with-shell forever?GRAILS_PREFERRED_SHELLis unset? Docs and code disagree today.-tis wrapper-type vs Forge--test. What should flags mean?-cliartifacts?create-controllerexists in both CLIs and is not the same implementation.run-app, interactive mode, and IntelliJ integration live if shell is no longer the generator?ApplicationCommand/ Grails 8-cliartifacts relative to profile YAML commands and Forge codegen?Drift and maintenance
rx-mongodb/geb2/ SNAPSHOT security pin; ForgeCategoryleftovers,Language/BuildTool/TestFrameworkenums)?Docs and distribution
create-app(the chapter currently tells both stories).list-profilesdocs../grailswrapper.Pointers into the tree
grails-forge/grails-cli/src/main/groovy/org/apache/grails/cli/DelegatingShellApplication.groovygrails-shell-cli/src/main/groovy/org/grails/cli/profile/commands/CreateAppCommand.groovygrails-shell-cli/src/main/groovy/org/grails/cli/profile/AbstractProfile.groovygrails-profiles/{base,web,plugin,web-plugin,rest-api,rest-api-plugin,profile}/grails-forge/grails-forge-core/src/main/java/org/grails/forge/application/generator/DefaultProjectGenerator.java…/application/ApplicationType.java…/feature/grailsProfiles/GrailsProfiles.javagrails-forge/grails-forge-cli/src/main/java/org/grails/forge/cli/Application.javagrails-doc/src/en/guide/commandLine.adoc,profiles.adoc,gettingStarted/creatingAnApplication.adocHackathon context: Groovy & Grails at Community over Code Glasgow 2026.