Skip to content

Grails Forge, Profiles, and CLI: inventory of the two application generators #16348

Description

@jamesfredley

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:

Generator Entry points Technology Lives in
Profiles grails create-app (shell CLI default), create-plugin, create-web-plugin, create-profile 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.


Current topology (what a user actually runs)

grails  (SDKMAN / distro / project wrapper)
  └── grails-cli  DelegatingShellApplication
        ├── GRAILS_PREFERRED_SHELL=shell|forge   (default: shell)
        └── first args -t/--type shell|forge
              ├── forge → org.grails.forge.cli.Application   (Picocli, Micronaut DI)
              └── shell → org.grails.cli.GrailsCli           (JLine, profiles, Gradle Tooling API)

Quirks of the dispatcher:

  • 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:

Artifact / module Class Role
grails-cli (shadow) DelegatingShellApplication User-facing grails binary
grails-forge-cli org.grails.forge.cli.Application Generation + limited codegen
grails-shell-cli org.grails.cli.GrailsCli Generation via profiles + project lifecycle + profile commands + ApplicationCommands

How Profiles generate an application

Profile artifacts

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.yml extends: (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:

  • description, instructions (printed after create)
  • repositories, build.repositories, build.plugins, build.excludes
  • dependencies (scope + coords + optional transitive excludes; excludes scope strips parent deps)
  • features.defaults, features.required
  • skeleton.excludes, skeleton.parent.target (multi-project), skeleton.binaryExtensions, skeleton.executable (Ant patterns, chmod)
  • command.excludes
  • commands map (class name → groovy/yml) — used less than directory-scanned commands

Generation algorithm (CreateAppCommand.handle)

  1. Resolve profile name (--profile or default: web / plugin / web-plugin / profile depending on command).
  2. Resolve the profile JAR via MavenProfileRepository + Eclipse Aether (not Gradle). Repos from ~/.grails/settings.groovy grails.profiles.repositories, plus ~/.m2/settings.xml for proxies/auth. SNAPSHOT Grails versions add ASF snapshot and Sitemesh snapshot repos into the generated build.
  3. 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.
  4. Parse app name: com.example.myapp → group com.example, app myapp; bare myapp → synthesized package from hyphenated tokens. --inplace uses the current directory name.
  5. Refuse non-empty target directories.
  6. 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.
  7. Copy each selected feature's skeleton/ the same way; append feature application.yml / build.gradle / gradle.properties.
  8. replaceBuildTokens: inject @dependencies@, @buildPlugins@, @repositories@, @buildDependencies@, @buildRepositories@, plus the variable map. Always add profile "org.apache.grails.profiles:<name>" as a Gradle profile scope dependency.
  9. chmod executable patterns (gradlew*, grailsw*).
  10. Print instructions if any.

Skeleton tokens (undocumented in the user guide — #11496)

Set in CreateAppCommand.initializeVariables:

Token Meaning
@grails.codegen.defaultPackage@ Java package
@grails.codegen.defaultPackage.path@ package as path
@grails.codegen.projectClassName@ e.g. Myapp
@grails.codegen.projectNaturalName@ e.g. Myapp natural
@grails.codegen.projectName@ script name
@grails.profile@ profile name
@grails.version@ Grails version
@grails.app.name@ / @grails.app.group@ app / group
@APPNAME@ raw app name
@gorm.version@, @groovy.version@, @grails-gradle-plugins.version@ from profile dependency versions when using MavenProfileRepository

Command templates later use @artifact.package.path@, @artifact.name@, @artifact.propertyName@.

Shipping profiles and their features

Profile Default features Required features Notable extras
base events Gradle app plugin, BOM, logging, validation, boot starter, grails-core, Spock, datamapping test support, devtools. Not created directly.
web hibernate5, geb2 gsp, asset-pipeline WAR, grails-web, actuator, Tomcat, GSP-related modules, sitemesh3, scaffolding, Geb testFixtures, Bootstrap/jQuery webjars
plugin grails-plugin Gradle plugin, grails-core only. Features: maven-publish, asset-pipeline-plugin
web-plugin asset-pipeline-plugin gsp Merges base+web+plugin; excludes WAR and grails-web plugin; excludes many skeleton files (assets, i18n, spring, UrlMappings, BootStrap); excludes war command
rest-api hibernate5 json-views WAR, grails-web, JSON views testing support. Feature: security (Spring Security REST, pinned 7.0.0-SNAPSHOT)
rest-api-plugin hibernate5 json-views 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.
  • neo4j: org.grails.plugins:neo4j — same vintage.
  • json-views: also pulls grails-data-mongodb-gson-templates even 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).
  • 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 --features drops 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:

  1. Hard-coded ServiceLoader commands (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.
  2. CommandFactory implementations: YAML files, Groovy scripts, ServiceLoader commands, ApplicationContextCommandFactory (adapts Gradle ApplicationCommands / -cli artifacts to the shell).
  3. The resolved profile JAR (and parents): commands/*.yml and commands/*.groovy.
  4. The application itself: 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 artefact create-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 Groovy generate-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, 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)
grails-cli / grails-cli-shadow Delegating wrapper
test-core Generation tests

Application types

ApplicationType enum: WEB, REST_API, WEB_PLUGIN, PLUGIN. Default WEB.

No REST_API_PLUGIN. No PROFILE. Tracked as #14932 (blocked on #14944).

Each type has its own AvailableFeatures bean (WebAvailableFeatures, …) so the selectable set differs.

Generation algorithm (DefaultProjectGenerator + ContextFactory)

  1. Parse name (NameUtils.parse) — package + app from dotted name; --inplace uses current dir.
  2. Build Options (reloading, GORM, servlet, JDK, OS). Nulls become defaults: DevTools, Hibernate 5, Tomcat, JDK 21.
  3. Resolve named features; unknown feature names fail (IllegalArgumentException).
  4. 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).
  5. validatePreProcessing (e.g. OneOfFeature groups: one servlet, one GORM, one logging config, one primary security).
  6. processSelectedFeatures — features may add/exclude others (getDependentFeatures is descriptive metadata for UIs).
  7. validatePostProcessing.
  8. 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).
  9. apply each feature: add Rocker/URL templates, Gradle deps/plugins, configuration keys, binary resources.
  10. 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 --features replaces 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).

Forge feature inventory (names)

Always-on / often invisible (DefaultFeature, many isVisible() == 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 say grails-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: 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_pluginweb-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.

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 / 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.

Forge HTTP / website

  • UI: https://start.grails.org (React, apache/grails-forge-ui)
  • 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.


Compare and contrast

Surfaces

Capability Profiles + Shell Forge
CLI create yes (default grails create-app) yes (grails -t forge …)
Website no start.grails.org
HTTP / curl ZIP no yes, versioned slots
Preview / diff no yes
Custom third-party generators yes (publish a profile JAR) no (features are code in grails-forge-core)
create-profile meta-generator yes no
Interactive JLine + completers yes Picocli interactive create/codegen only
Drive Gradle (run-app, test-app, …) yes no
IntelliJ Grails plugin historically shell no
SDKMAN grails binary wrapper → both wrapper → both

Application types

Type Profile Forge ApplicationType
web yes (default) WEB
rest-api yes REST_API
plugin yes PLUGIN
web-plugin yes WEB_PLUGIN
rest-api-plugin yes missing (#14932)
profile (author a profile) yes missing (intentional per #14932 text)
base exists, not user-facing hidden base feature, different meaning

Feature model

Profiles Forge
Definition feature.yml + skeleton files Java Feature class + Rocker/URL templates
Combinatorics defaults + required; --features replaces defaults defaults always applied; --features additive; OneOfFeature validators
Unknown name warn, continue fail
Exclusive groups no yes (GORM, servlet, logging, primary security)
JDK no 21/25/26
Servlet choice Tomcat hardcoded in web/rest-api tomcat/jetty/undertow/none
Hibernate 7 + matching BOM no yes
Micronaut BOM switch no grails-micronaut / hibernate7-micronaut BOM
GitHub Actions workflow no github-workflow-java-ci
Database drivers (mysql/postgres/sqlserver) no first-class (manual deps) features
Testcontainers / Geb variants geb2 (mostly empty) geb-with-testcontainers, geb-with-webdriver-binaries, testcontainers
Spring Security rest-api security feature (REST, SNAPSHOT pin); no web-app equivalent grails-spring-security, UI, Boot starter
Quartz no grails-quartz + create-job
Cache / Asciidoctor / shade / properties no / no / no / no yes
Company-specific feature without a Forge PR yes (own profile) no

Generated build / config drift (known)

#14944 / #14933 document concrete coordinate differences. Examples:

  • Forge plugins include Hibernate, H2, Hikari, i18n, interceptors, url-mappings, web-boot, … that the plugin profile does not.
  • Forge web-plugin adds grails-layout, H2, Hikari, jansi that the web-plugin profile may not.
  • rest-api profile includes grails-async; Forge rest-api may not (Update Profile Dependencies to more closely match Grails Forge Defaults #14933).
  • 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.

Post-create artefact commands

Command Shell / profile Forge codegen
create-controller YAML, + empty views dir (web) Rocker, controller + spec only
create-service YAML Rocker
create-domain-class YAML (base) Rocker
create-taglib YAML (web) Rocker
create-interceptor YAML Rocker
create-unit-test / integration-test / functional-test YAML no
create-restful-controller / create-domain-resource rest-api YAML no
generate-all / generate-controller / generate-views rest-api Groovy no (Gradle scaffolding -cli is a separate path)
create-job no yes if quartz feature
create-script / create-command base no
add-property Groovy Forge has AddPropertyCommand
run-app / test-app / console / stats / … yes no

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

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

  1. Is the long-term generator the Forge feature/Rocker engine, the profile skeleton/YAML engine, a shared template store both call, or something else?
  2. If one engine remains, what happens to the other's unique types (rest-api-plugin, create-profile, third-party published profiles)?
  3. 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?
  4. How should Hibernate 7 / Micronaut BOM switching work if profiles remain a generator?
  5. How should exclusive options (servlet, GORM, JDK) work if profiles remain a generator?
  6. What is the compatibility story for existing ~/.grails/settings.groovy profile repos and in-house company profiles?

CLI binary and command structure

  1. What is the user-facing command set of grails going forward? One tree, or generate-with-Forge / operate-with-shell forever?
  2. What should the default be when GRAILS_PREFERRED_SHELL is unset? Docs and code disagree today.
  3. -t is wrapper-type vs Forge --test. What should flags mean?
  4. Which artefact commands belong in the CLI vs Gradle vs -cli artifacts? create-controller exists in both CLIs and is not the same implementation.
  5. Where do run-app, interactive mode, and IntelliJ integration live if shell is no longer the generator?
  6. Can Picocli (Forge) and JLine/profile commands (shell) share completion and help, given the Micronaut-bean nesting constraint on the wrapper?
  7. What is the story for ApplicationCommand / Grails 8 -cli artifacts relative to profile YAML commands and Forge codegen?

Drift and maintenance

  1. While two generators exist, what is the source of truth for default dependencies? (Forge vs Profile dependencies for plugin and web-plugin #14944 / Update Profile Dependencies to more closely match Grails Forge Defaults #14933 are symptoms.)
  2. Is matching "as much as possible" (Update 7.0.x profiles to generate valid Grails 7 projects #14699) still the policy, or is divergence acceptable until a cutover?
  3. 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

  1. One user-guide story for create-app (the chapter currently tells both stories).
  2. Website / curl / version slots if Forge is not the generator — or if it is the only generator, what happens to list-profiles docs.
  3. SDKMAN contents, native images, and the project ./grails wrapper.

Pointers into the tree

  • Dispatcher: grails-forge/grails-cli/src/main/groovy/org/apache/grails/cli/DelegatingShellApplication.groovy
  • Shell generator: grails-shell-cli/src/main/groovy/org/grails/cli/profile/commands/CreateAppCommand.groovy
  • Profile model: grails-shell-cli/src/main/groovy/org/grails/cli/profile/AbstractProfile.groovy
  • Profile JARs: grails-profiles/{base,web,plugin,web-plugin,rest-api,rest-api-plugin,profile}/
  • Forge generator: grails-forge/grails-forge-core/src/main/java/org/grails/forge/application/generator/DefaultProjectGenerator.java
  • Forge types: …/application/ApplicationType.java
  • Forge still emitting profiles: …/feature/grailsProfiles/GrailsProfiles.java
  • Forge CLI: grails-forge/grails-forge-cli/src/main/java/org/grails/forge/cli/Application.java
  • User guide: grails-doc/src/en/guide/commandLine.adoc, profiles.adoc, gettingStarted/creatingAnApplication.adoc

Hackathon context: Groovy & Grails at Community over Code Glasgow 2026.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions