Polish all 9 v8 guides for the imminent Grails 8 release#506
Merged
jamesfredley merged 10 commits intomasterfrom May 4, 2026
Merged
Polish all 9 v8 guides for the imminent Grails 8 release#506jamesfredley merged 10 commits intomasterfrom
jamesfredley merged 10 commits intomasterfrom
Conversation
Apache Grails 8 ships in the coming weeks. The prev-snapshot.grails.org forge that the v8 guides currently point at will go stale on release. Switch every reference to the canonical link:https://start.grails.org[] forge UI per the existing pattern in grails-fields-custom-widgets-and-wrappers v8. The mechanical changes: * createApp.adoc / download.adoc / howto.adoc across grails-tailwindcss, grails-github-actions-cicd, grails-rest-library, grails-htmx, grails-multi-module, grails-fields-custom-widgets-and-wrappers, and grails-docker-bootbuildimage all swap prev-snapshot.grails.org for start.grails.org. * The download.adoc curl examples are rewritten as browser-based instructions ("Open start.grails.org, pick the X profile, name the app Y, set the package to Z, tick features W, download the zip") because start.grails.org is a browser UI; its REST forge endpoint lives on a separate Cloud Run host that is not part of the user-facing interface. * The grails-multi-module generateModules.adoc collapses the three curl commands into a single browser walkthrough plus an unzip + rename block. * conf/guides.yml: four "Download a Grails 8 Snapshot Starter" TOC titles drop the "Snapshot" word now that 8 is shipping; the "rest_api Starter" title is normalised to "Rest API Starter". Assisted-by: claude-code:claude-4.6-opus
…uild refinements The asset-pipeline integration was correct end-to-end but only mentioned once, in a NOTE block at the bottom of gradleWiring.adoc. A reader who skimmed could easily miss that the asset pipeline is what bundles, fingerprints, gzips, and serves the compiled Tailwind CSS as part of application.css. Narrative changes: * whatYouWillBuild.adoc adds the explicit "...where the asset pipeline bundles, fingerprints, and gzips that file into the application.css bundle the layout serves" sentence. * gradleWiring.adoc promotes the asset-pipeline manifest from the NOTE sidebar into body prose, naming the *= require app directive and the asset:stylesheet taglib that the next chapter uses. * firstPaint.adoc adds a one-line callout pointing at <asset:stylesheet src="application.css"/> as the asset-pipeline taglib that resolves the manifest at runtime. Snippet refinements: * build.gradle uses Provider syntax for tailwindBuild and npmInstall dependsOn (more idiomatic than the string form), declares package-lock.json as an extra input, scans grails-app/services, grails-app/taglib, and src/main/groovy in addition to views and controllers, and adds testAndDevelopmentOnly Bootstrap webjars so the dev console pages still render. * src/main/css/input.css adds tag::components / end::components markers so componentLayer.adoc can include only the relevant block via include::../snippets/src/main/css/input.css[tag=components,indent=0]. * application.css manifest collapses to *= require app. * main.gsp and index.gsp are tightened to match the chapter walkthrough. * package.json bumps to a current Tailwind 4 line. Across the chapters, every section that touches a file ends with a "Files touched" bullet list to match the established grails-fields-custom-widgets-and-wrappers v8 style and a couple of chapter-end pointers to the matching grails-doc reference manual sections (deployment.adoc et al.). Assisted-by: claude-code:claude-4.6-opus
In Grails 8 the canonical (and only fully supported) Geb base class is
grails.plugin.geb.ContainerGebSpec from
testFixtures("org.apache.grails:grails-geb"). It starts a
Selenium-Chrome browser inside a Testcontainers container, removing
the need for host-side WebDriver binaries entirely. The
geb-with-webdriver-binaries forge feature from earlier Grails majors
is obsolete - keeping it adds a second Chrome driver no test ever
uses.
Changes:
* functionalTest.adoc rewrites the chapter narrative around
ContainerGebSpec, calling out the mandatory @Integration annotation
(the Spock extension throws IllegalArgumentException at runtime
without it), the absence of @Rollback (functional tests need
committed data), and the Docker daemon prerequisite.
* New BookFunctionalSpec.groovy snippet under
src/integration-test/groovy/example/, vendored so the chapter can
include::../snippets/...[] like every other code block in the
guide. The spec extends grails.plugin.geb.ContainerGebSpec and
carries inline Javadoc explaining why @Integration is mandatory.
* whatYouWillBuild.adoc updates the spec inventory from "four spec
files exercising four test layers" to "five spec files exercising
the five test layers", since the functional layer is now first
class.
* howto.adoc drops the geb-with-webdriver-binaries feature from the
required forge feature list.
* conf/guides.yml: subtitle now mentions ContainerGebSpec; the
functionalTest TOC title updates from "Geb Functional Tests" to
"ContainerGebSpec Functional Tests".
Verified against grails-core 8.0.x at
grails-geb/src/testFixtures/groovy/grails/plugin/geb/.
Assisted-by: claude-code:claude-4.6-opus
The functional-test job in the CI workflow now runs Geb specs that
extend grails.plugin.geb.ContainerGebSpec, so the Selenium-Chrome
browser lives inside a Testcontainers container. The legacy
geb-with-webdriver-binaries forge feature - and its companion
webdriver-binaries-gradle-plugin - are obsolete in Grails 8 and
removed throughout.
Changes:
* createApp.adoc: drop geb-with-webdriver-binaries from the required
forge feature list (postgres + testcontainers are sufficient; the
ContainerGebSpec test fixtures already ship in the standard web
profile).
* download.adoc: remove the legacy
integrationTestImplementation testFixtures("org.apache.grails:grails-geb")
+ webdriver-binaries Gradle plugin pair from the chapter narrative.
* functionalTests.adoc: rewrite the chapter to describe Selenium-in-
Testcontainers (no host-side Chrome install needed; first cold run
pulls selenium/standalone-chrome ~200 MB; subsequent runs reuse
the cached layer).
* howto.adoc: drop the geb-with-webdriver-binaries feature mention
from the prereq summary.
* The other chapters (badge, branchProtection, ciYml, dependabot,
ghcrPush, integrationTests, releaseYml, unitTests, workflowOverview,
whatYouWillBuild, requirements, gettingStarted, helpWithGrails)
pick up the consistent "Files touched" bullet style and minor
prose tightening.
Verified against grails-core 8.0.x at
grails-geb/src/testFixtures/groovy/grails/plugin/geb/.
Assisted-by: claude-code:claude-4.6-opus
…or walkthrough
The most common day-to-day question for a multi-project Grails build is
"how do I save a file in shared-core and have my running webapp
bootRun pick it up?". Grails 8 answers this with a first-class plugin
- org.apache.grails.gradle.grails-exploded - that publishes exploded
classes/resources from a Grails Plugin subproject so the consuming
webapp's bootRun can request the exploded variant automatically.
Source: grails-gradle/plugins/src/main/groovy/org/grails/gradle/plugin/exploded/GrailsExplodedPlugin.groovy.
Changes:
* New chapter devReload.adoc walks the supported recipe (apply
org.apache.grails.gradle.grails-exploded in shared-core, declare
the plugin under the grails { plugins { ... } } block in each
webapp), includes a what-reloads vs what-restarts table, and keeps
the manual bootRun classpath patch + spring.devtools.restart.additional-paths
approach as alternatives in NOTE blocks.
* runningEach.adoc forward-references the new chapter, documents
bootJar deployment, and clarifies that shared-core does not produce
a deployable bootJar (GrailsPluginGradlePlugin disables bootJar for
the plugin project).
* sharedCore.adoc expands the plugin descriptor explanation with
source citations: grails-core/src/main/groovy/grails/plugins/Plugin.groovy
for the lifecycle hooks; grails-core/src/main/groovy/org/grails/core/artefact/DomainClassArtefactHandler.java,
grails-domain-class/src/main/groovy/org/grails/plugins/domain/support/DefaultMappingContextFactoryBean.groovy,
and grails-data-hibernate5/grails-plugin/src/main/groovy/grails/plugin/hibernate/HibernateGrailsPlugin.groovy
for cross-plugin domain auto-discovery.
* New shared-core/src/main/groovy/example/SharedCoreGrailsPlugin.groovy
snippet so sharedCore.adoc can include::../snippets/...[].
* snippets/build.gradle: collapse the dual content { } filter blocks
in the snapshots/staging Maven repos into a single block per repo
(separate blocks AND-combine, which silently disables the
repository - the intent is OR-combine), drop the redundant
plugins.withId('groovy') guard around useJUnitPlatform.
* snippets/settings.gradle: minor cleanup matching the chapter.
* All chapter prose picks up source-citation NOTE blocks and "Files
touched" bullets to match the established
grails-fields-custom-widgets-and-wrappers v8 style.
* conf/guides.yml: TOC entry for devReload between runningEach and
testingPerModule.
Assisted-by: claude-code:claude-4.6-opus
The TaskController action set is tightened for production hygiene: explicit flush:true on every save and delete (deterministic before the response is rendered), validate-before-save flow with explicit error rendering, field-by-field assignment instead of mass assignment through params (the original 't.properties = params' allowed arbitrary fields to slip through), and a new show(Long id) action. A new _taskCreated.gsp partial demonstrates the canonical htmx out-of-band swap pattern: the response renders the new task into #taskList while replacing the form with a fresh empty one, all in a single round trip - a teaching point the chapter narrative now covers explicitly. Other view-side cleanups: * _taskForm.gsp: g:form + g:textField + g:hasErrors structure with encodeAsHTML on error message output. * _task / _taskEdit / _taskRows / index / main: consistent formatting, attribute ordering, and label improvements. * UrlMappings.groovy and Task.groovy: minor cosmetic alignment with the chapter walkthrough. Each chapter ends with the established "Files touched" bullet list. Assisted-by: claude-code:claude-4.6-opus
…SON views
The bulk-create handler had a real operator-precedence bug:
if (!request.JSON instanceof List) { ... }
parses as
if ((!request.JSON) instanceof List) { ... }
which is always false (a falsy value 'instanceof List' is never true),
so the body-shape guard never fired and a single-object request body
silently bound nothing. The fix is the parenthesised form
'if (!(request.JSON instanceof List))'.
The handler is also modernised to current Grails 8 idioms: bindData()
instead of 'new Book(json as Map)' (so domain-class binding rules
fire); errors.allErrors instead of errors.fieldErrors (catches
global errors too); message(error: error) for i18n message resolution
through the standard Grails MessageSource; and failOnError: true on
the final per-row save so a validation slip cannot reach
production. The success path now respond()s through the index view
template so the response shape stays consistent with the regular
list endpoint.
A new index() override paginates the list with a sensible 25/100
default/cap; a new countResources() override surfaces a bookCount
in the model so JSON views can render a total. Both honour an
optional ?author=<id> filter via Book.where { author.id == authorId }.
Other refreshes:
* AuthorController.groovy and BootStrap.groovy align with the same
patterns.
* All .gson views (_book, _author, index, show) get a cleaner shape
matching the chapter walkthrough.
* New _errors.gson view: structured 422 error response shape used by
the validation chapter.
* New changelog.groovy under grails-app/migrations/: the Liquibase
entry-point the database-migration feature requires; the chapter
references it via include::../snippets/...[].
Assisted-by: claude-code:claude-4.6-opus
…ocker-bootbuildimage v8 The bootBuildImage chapter now explicitly cites the Spring Boot Gradle plugin coordinate (spring-boot-gradle-plugin:4.0.5) verified against grails-core 8.0.x dependencies.gradle. The whatYouWillBuild chapter notes that the official Apache Grails 8 reference manual at grails-doc/src/en/guide/deployment.adoc covers WAR-file deployment to a servlet container, while this guide complements that with the modern OCI-image path enabled by Spring Boot 4. Each chapter (whatYouWillBuild, dockerOverview, bootBuildImage, runImage, envProfiles, postgresWiring, compose, healthCheck, jvmContainer, nonRoot, dockerfile, ghcr) closes with the established "Files touched" bullet list so a reader can scan exactly which on-disk files each chapter expects them to edit. Assisted-by: claude-code:claude-4.6-opus
The cors.adoc chapter previously claimed there was nothing
Grails-specific for CORS handling and pointed only at the Spring Boot
CORS reference. That was wrong: grails-doc/src/en/guide/theWebLayer/cors.adoc
documents Grails 8's first-class grails.cors.enabled configuration
block, which produces a Spring CorsConfiguration mapped to /** by
default and supports per-mapping overrides.
The chapter now shows the canonical grails.cors block:
grails:
cors:
enabled: true
allowedOrigins:
- 'https://app.example.com'
...
while keeping the original recommendation that the same-origin layout
this guide builds removes the need for CORS entirely - the Grails
config is only relevant if you split the SPA and the API across
origins.
Other chapter additions:
* spaController.adoc cites the underlying source for the forward
method (grails-controllers/src/main/groovy/grails/web/util/WebUtils.java)
and the user-facing reference at
grails-doc/src/en/guide/theWebLayer/controllers/forwarding.adoc.
* urlMappings.adoc points at grails-doc/src/en/guide/theWebLayer/urlMappings.adoc
and the restfulMappings sub-chapter.
* devMode.adoc references grails-doc/src/en/guide/gettingStarted/developmentReloading.adoc
for the Spring DevTools story.
* whatYouWillBuild.adoc and backend.adoc add the matching
grails-doc/src/en/guide/REST/ pointers.
Every substantive chapter ends with a "Files touched" bullet list
matching the established style.
Assisted-by: claude-code:claude-4.6-opus
…ustom-widgets-and-wrappers v8 grails-doc/src/en/guide/theWebLayer/fields.adoc (and its fields/ subdirectory) is the official 8.0.x reference for the Fields plugin high-level f: taglib API. This guide complements it with worked examples of customising the per-property and per-class templates that the plugin's FormFieldsTemplateService looks up. Changes: * fieldsPluginAlreadyIncluded.adoc: drop the stale '8.0.0-SNAPSHOT' hard-coded version reference (matches the grails-core release pinned by the consumer's project), name the taglib path 'grails-fields/grails-app/taglib/grails/plugin/formfields/FormFieldsTagLib.groovy', and add the cross-reference to grails-doc/src/en/guide/theWebLayer/fields.adoc. * templateLookup.adoc: cite the source path for the lookup logic (grails-fields/src/main/groovy/grails/plugin/formfields/FormFieldsTemplateService.groovy) and document the additional _themes/<theme>/ lookup level controlled by grails.plugin.fields.theme (THEMES_FOLDER constant in FormFieldsTemplateService). Assisted-by: claude-code:claude-4.6-opus
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Comprehensive pre-release polish pass on every v8 guide:
prev-snapshot.grails.orgreference (the snapshot forge will go stale on release) in favour ofstart.grails.orgper the established pattern ingrails-fields-custom-widgets-and-wrappers v8. Thecurlexamples are rewritten as browser-based instructions becausestart.grails.orgis a UI; its REST forge endpoint lives on a separate Cloud Run host that is not part of the user-facing interface. Four "Snapshot Starter" TOC titles drop the "Snapshot" word.grails-spock-test-tour v8: migrate the functional-test chapter fromGebSpectograils.plugin.geb.ContainerGebSpec(the canonical Grails 8 base class - Selenium runs inside a Testcontainers container, no host WebDriver binaries needed). Vendor a newBookFunctionalSpec.groovysnippet, dropgeb-with-webdriver-binariesfrom the forge feature list, and update the registry subtitle + TOC title.grails-github-actions-cicd v8: align the CI/CD job graph with the sameContainerGebSpecstory.grails-multi-module v8: add a newdevReload.adocchapter walking the supportedorg.apache.grails.gradle.grails-explodedplugin recipe (verified atgrails-gradle/plugins/src/main/groovy/org/grails/gradle/plugin/exploded/GrailsExplodedPlugin.groovyon8.0.x) plus thegrails { plugins { ... } }wiring on the consuming webapp side. ExpandsharedCore.adocwith cited source paths for the plugin descriptor, GORM cross-plugin auto-discovery, and the Gradle plugin lifecycle. Fix a real Gradle bug in the snippetbuild.gradle: dualcontent { }filter blocks AND-combine and silently disabled the snapshot/staging Maven repos forgrails*artefacts.grails-htmx v8: refactorTaskControllerfor production hygiene (explicitflush:true, validate-then-save, field-by-field assignment instead oft.properties = params, newshow(Long id)), add a new_taskCreated.gsppartial demonstrating the canonical htmx out-of-band swap pattern, encode error messages as HTML, and add thehtmx 2.0.4webjar dependency.grails-rest-library v8: fix a real operator-precedence bug inBookController.bulkCreate(!request.JSON instanceof Listparses as(!request.JSON) instanceof Listwhich is always false). Modernise tobindData(),errors.allErrors, and i18nmessage(error: error). Add pagination (index(Integer max)+countResources()) and an?author=<id>filter viaBook.where { author.id == authorId }. New_errors.gsonview + new Liquibasechangelog.groovyso the database-migration feature has its entry-point.grails-tailwindcss v8: tighten the asset-pipeline narrative acrosswhatYouWillBuild,gradleWiring, andfirstPaintso the<asset:stylesheet>taglib +application.cssmanifest path is in the body prose, not a sidebarNOTE.tailwindBuild/npmInstalluse Provider-baseddependsOnand pick upservices/,taglib/, andsrc/main/groovyas extra source dirs.input.cssgetstag::components/end::componentsmarkers socomponentLayer.adoccaninclude::only the relevant block.grails-docker-bootbuildimage v8: cite the Spring Boot Gradle plugin coordinate (spring-boot-gradle-plugin:4.0.5, verified againstgit show 8.0.x:dependencies.gradle), cross-referencegrails-doc/src/en/guide/deployment.adocfor WAR-file deployment as a complementary path.grails-vite-spa v8: critical fix - thecors.adocchapter previously claimed Grails has no built-in CORS support. Wrong:grails-doc/src/en/guide/theWebLayer/cors.adocdocuments thegrails.cors.enabledblock. Rewrote with the canonical YAML config. Cross-referencegrails-doc/src/en/guide/REST/,theWebLayer/urlMappings.adoc,theWebLayer/controllers/forwarding.adoc, andgettingStarted/developmentReloading.adoc.grails-fields-custom-widgets-and-wrappers v8: cross-reference the officialgrails-doc/src/en/guide/theWebLayer/fields.adocchapter, drop the stale hard-coded8.0.0-SNAPSHOTversion reference, name the taglib source path, and document the additional_themes/<theme>/lookup level controlled bygrails.plugin.fields.theme.Files touchedbullet list matching the establishedgrails-fields-custom-widgets-and-wrappers v8style.Verification
./gradlew --no-configuration-cache renderGuide_grails_*_8PASSES for all 9 v8 guides (HTML inbuild/dist/guides/grails-*/8/).Cross-references verified against
grails-core8.0.x viagit -C /home/james/projects/grails-core show 8.0.x:<path>(read-only; never switched branches).Companion sample-repo updates
The matching
grails-guides/<name>sample repos on thegrails8branch have been updated in lock-step (7 of 9 needed changes;docker-bootbuildimageandvite-spawere already in sync). New head commits:grails-spock-test-tour@126c7d8grails-htmx@b682b98grails-rest-library@36507fagrails-multi-module@34415a5grails-tailwindcss@18f48e7grails-github-actions-cicd@b16323cgrails-fields-custom-widgets-and-wrappers@41b5737