You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@grails.apache.org by ja...@apache.org on 2026/10/08 19:27:51 UTC

(grails-static-website) 01/01: Update Grails 8 guide snippets for the 8.0.0 release.

This is an automated email from the ASF dual-hosted git repository.

jamesfredley pushed a commit to branch grails-8.0.0-snippets
in repository https://gitbox.apache.org/repos/asf/grails-static-website.git

commit 8c54bb69e0246fb3f3ba8243339fa90db78d6038
Author: James Fredley <ja...@users.noreply.github.com>
AuthorDate: Thu Oct 8 15:27:43 2026 -0400

    Update Grails 8 guide snippets for the 8.0.0 release.
    
    Pin the v8 guide samples and prose to Grails 8.0.0 and Gradle 9.8.0, and match the constraint, count, save-order, and plugin message-bundle fixes required by the GA runtime.
    
    Assisted-by: opencode:grok-4.7
---
 .../creating-your-first-grails-app/v8/guide/creatingTheApp.adoc   | 2 +-
 guides/database-per-tenant/v8/snippets/gradle.properties          | 2 +-
 .../v8/snippets/grails-app/domain/example/Author.groovy           | 2 +-
 .../v8/snippets/grails-app/domain/example/Book.groovy             | 2 +-
 .../v8/snippets/grails-app/domain/example/Tag.groovy              | 2 +-
 guides/grails-docker-bootbuildimage/v8/snippets/build.gradle      | 1 -
 .../v8/guide/bootstrapData.adoc                                   | 2 +-
 .../v8/snippets/grails-app/domain/example/Author.groovy           | 6 +++---
 .../v8/snippets/grails-app/domain/example/Book.groovy             | 6 +++---
 .../v8/snippets/grails-app/init/example/BootStrap.groovy          | 8 ++++++--
 .../v8/snippets/grails-app/domain/example/Message.groovy          | 2 +-
 guides/grails-htmx/v8/guide/taskDomain.adoc                       | 2 +-
 .../grails-htmx/v8/snippets/grails-app/domain/example/Task.groovy | 2 +-
 guides/grails-jobrunr/v8/guide/gettingStarted.adoc                | 2 +-
 guides/grails-jobrunr/v8/guide/jobRequests.adoc                   | 2 +-
 guides/grails-jobrunr/v8/guide/requirements.adoc                  | 2 +-
 guides/grails-jobrunr/v8/snippets/complete/gradle.properties      | 2 +-
 .../complete/grails-app/domain/example/grails/Delivery.groovy     | 2 +-
 guides/grails-jobrunr/v8/snippets/initial/gradle.properties       | 2 +-
 guides/grails-multi-module/v8/guide/failureModes.adoc             | 4 +++-
 .../v8/snippets/shared-core/grails-app/domain/example/Book.groovy | 4 ++--
 guides/grails-rest-library/v8/guide/domainModel.adoc              | 2 +-
 .../snippets/grails-app/controllers/example/BookController.groovy | 8 ++++----
 .../v8/snippets/grails-app/domain/example/Author.groovy           | 2 +-
 .../v8/snippets/grails-app/domain/example/Book.groovy             | 5 +++--
 .../v8/snippets/grails-app/views/author/index.gson                | 2 +-
 .../v8/snippets/grails-app/views/book/index.gson                  | 2 +-
 .../v8/snippets/grails-app/domain/example/Book.groovy             | 4 ++--
 guides/grails-spring-security/v8/snippets/build.gradle            | 2 +-
 .../v8/snippets/grails-app/domain/example/User.groovy             | 2 +-
 guides/grails-tailwindcss/v8/snippets/build.gradle                | 1 -
 .../grails-transactional-events/v8/guide/testingThePattern.adoc   | 2 +-
 .../v8/snippets/grails-app/domain/example/AuditLog.groovy         | 2 +-
 .../v8/snippets/grails-app/domain/example/Customer.groovy         | 2 +-
 .../v8/snippets/grails-app/domain/example/CustomerRequest.groovy  | 2 +-
 .../v8/snippets/grails-app/domain/example/Order.groovy            | 3 ++-
 .../v8/snippets/grails-app/domain/example/WorkOrder.groovy        | 3 ++-
 .../snippets/grails-app/domain/example/WorkOrderAssignee.groovy   | 3 ++-
 .../v8/snippets/backend/grails-app/domain/example/Book.groovy     | 6 +++---
 .../v8/snippets/backend/grails-app/views/book/index.gson          | 2 +-
 40 files changed, 61 insertions(+), 53 deletions(-)

diff --git a/guides/creating-your-first-grails-app/v8/guide/creatingTheApp.adoc b/guides/creating-your-first-grails-app/v8/guide/creatingTheApp.adoc
index dddde44944d..054f50cfeb7 100644
--- a/guides/creating-your-first-grails-app/v8/guide/creatingTheApp.adoc
+++ b/guides/creating-your-first-grails-app/v8/guide/creatingTheApp.adoc
@@ -3,7 +3,7 @@ You can recreate `initial/` yourself with the Grails Application Forge:
 [source,bash]
 ----
 curl -O https://start.grails.org/creating-your-first-grails-app.zip \
-  -d version=8.0.0-SNAPSHOT \
+  -d version=8.0.0 \
   -d profile=web \
   -d features=gsp,hibernate5,scaffolding,geb,h2
 unzip creating-your-first-grails-app.zip
diff --git a/guides/database-per-tenant/v8/snippets/gradle.properties b/guides/database-per-tenant/v8/snippets/gradle.properties
index 333a54c7088..40d30c9eeaa 100644
--- a/guides/database-per-tenant/v8/snippets/gradle.properties
+++ b/guides/database-per-tenant/v8/snippets/gradle.properties
@@ -1,4 +1,4 @@
-grailsVersion=8.0.0-M5
+grailsVersion=8.0.0
 version=0.1
 org.gradle.caching=true
 org.gradle.daemon=true
diff --git a/guides/grails-data-access/v8/snippets/grails-app/domain/example/Author.groovy b/guides/grails-data-access/v8/snippets/grails-app/domain/example/Author.groovy
index 36f3be49cf4..67e10c3597b 100644
--- a/guides/grails-data-access/v8/snippets/grails-app/domain/example/Author.groovy
+++ b/guides/grails-data-access/v8/snippets/grails-app/domain/example/Author.groovy
@@ -11,7 +11,7 @@ class Author {
     static hasMany = [books: Book]
 
     static constraints = {
-        name blank: false, maxSize: 100
+        name nullable: false, blank: false, maxSize: 100
         email email: true, unique: true, nullable: true, maxSize: 255
     }
 
diff --git a/guides/grails-data-access/v8/snippets/grails-app/domain/example/Book.groovy b/guides/grails-data-access/v8/snippets/grails-app/domain/example/Book.groovy
index 1b33088b809..73428d229c1 100644
--- a/guides/grails-data-access/v8/snippets/grails-app/domain/example/Book.groovy
+++ b/guides/grails-data-access/v8/snippets/grails-app/domain/example/Book.groovy
@@ -22,7 +22,7 @@ class Book {
     }
 
     static constraints = {
-        title blank: false, maxSize: 255
+        title nullable: false, blank: false, maxSize: 255
         isbn nullable: true, maxSize: 20
         price nullable: false, min: 0.01G, scale: 2
         publishedOn nullable: true
diff --git a/guides/grails-data-access/v8/snippets/grails-app/domain/example/Tag.groovy b/guides/grails-data-access/v8/snippets/grails-app/domain/example/Tag.groovy
index 7d40fec4842..62502254457 100644
--- a/guides/grails-data-access/v8/snippets/grails-app/domain/example/Tag.groovy
+++ b/guides/grails-data-access/v8/snippets/grails-app/domain/example/Tag.groovy
@@ -10,7 +10,7 @@ class Tag {
     static hasMany = [books: Book]
 
     static constraints = {
-        name blank: false, unique: true, maxSize: 50
+        name nullable: false, blank: false, unique: true, maxSize: 50
     }
 
     String toString() {
diff --git a/guides/grails-docker-bootbuildimage/v8/snippets/build.gradle b/guides/grails-docker-bootbuildimage/v8/snippets/build.gradle
index 9478a6c5268..d3b072a77a3 100644
--- a/guides/grails-docker-bootbuildimage/v8/snippets/build.gradle
+++ b/guides/grails-docker-bootbuildimage/v8/snippets/build.gradle
@@ -113,7 +113,6 @@ dependencies {
     console "org.apache.grails:grails-console"
     runtimeOnly "cloud.wondrify:asset-pipeline-grails"
     runtimeOnly "com.zaxxer:HikariCP"
-    runtimeOnly "org.fusesource.jansi:jansi"
     runtimeOnly "org.postgresql:postgresql"
     integrationTestImplementation testFixtures("org.apache.grails:grails-geb")
     testImplementation "org.apache.grails:grails-testing-support-datamapping"
diff --git a/guides/grails-fields-custom-widgets-and-wrappers/v8/guide/bootstrapData.adoc b/guides/grails-fields-custom-widgets-and-wrappers/v8/guide/bootstrapData.adoc
index 8d48eb3219f..0d22bd994cb 100644
--- a/guides/grails-fields-custom-widgets-and-wrappers/v8/guide/bootstrapData.adoc
+++ b/guides/grails-fields-custom-widgets-and-wrappers/v8/guide/bootstrapData.adoc
@@ -6,6 +6,6 @@ Replace `grails-app/init/example/BootStrap.groovy` with the following so that th
 include::../snippets/grails-app/init/example/BootStrap.groovy[]
 ----
 
-The seed runs inside a `Book.withTransaction { }` block. This matters for the `Book`-`Tag` many-to-many: a bare `save()` in `BootStrap` runs outside any transaction and the join-table rows are not reliably flushed, while `save(flush: true)` without a transaction throws `TransactionRequiredException`. Wrapping the whole seed in a transaction lets the books, authors, contact info, and the many-to-many join all commit together.
+The seed runs inside a `Book.withTransaction { }` block. This matters for the `Book`-`Tag` many-to-many: a bare `save()` in `BootStrap` runs outside any transaction and the join-table rows are not reliably flushed, while `save(flush: true)` without a transaction throws `TransactionRequiredException`. Wrapping the whole seed in a transaction lets the books, authors, contact info, and the many-to-many join all commit together. Save each `Author` before attaching `ContactInfo`. Grails 8 ins [...]
 
 Restart the application with `./gradlew bootRun`. The H2 database is in-memory by default, so the seed data is recreated on every startup.
diff --git a/guides/grails-fields-custom-widgets-and-wrappers/v8/snippets/grails-app/domain/example/Author.groovy b/guides/grails-fields-custom-widgets-and-wrappers/v8/snippets/grails-app/domain/example/Author.groovy
index edbcf8f1a09..13fcf2ce7af 100644
--- a/guides/grails-fields-custom-widgets-and-wrappers/v8/snippets/grails-app/domain/example/Author.groovy
+++ b/guides/grails-fields-custom-widgets-and-wrappers/v8/snippets/grails-app/domain/example/Author.groovy
@@ -11,9 +11,9 @@ class Author {
     static hasMany = [books: Book]
 
     static constraints = {
-        name blank: false, maxSize: 200
-        email email: true, blank: false
-        bio blank: false, maxSize: 4000, widget: 'textarea'
+        name nullable: false, blank: false, maxSize: 200
+        email nullable: false, email: true, blank: false
+        bio nullable: false, blank: false, maxSize: 4000, widget: 'textarea'
         website url: true, nullable: true
         contactInfo nullable: true
     }
diff --git a/guides/grails-fields-custom-widgets-and-wrappers/v8/snippets/grails-app/domain/example/Book.groovy b/guides/grails-fields-custom-widgets-and-wrappers/v8/snippets/grails-app/domain/example/Book.groovy
index 359256900c6..b2f687f8ded 100644
--- a/guides/grails-fields-custom-widgets-and-wrappers/v8/snippets/grails-app/domain/example/Book.groovy
+++ b/guides/grails-fields-custom-widgets-and-wrappers/v8/snippets/grails-app/domain/example/Book.groovy
@@ -14,10 +14,10 @@ class Book {
     static hasMany = [tags: Tag]
 
     static constraints = {
-        title blank: false, maxSize: 255
-        isbn blank: false, matches: /^(?:\d{10}|\d{13}|\d{3}-\d-\d{2}-\d{6}-\d)$/
+        title nullable: false, blank: false, maxSize: 255
+        isbn nullable: false, blank: false, matches: /^(?:\d{10}|\d{13}|\d{3}-\d-\d{2}-\d{6}-\d)$/
         genre inList: ['Fiction', 'Non-Fiction', 'Biography', 'Science', 'History', 'Poetry']
-        description blank: false, maxSize: 2000, widget: 'textarea'
+        description nullable: false, blank: false, maxSize: 2000, widget: 'textarea'
         publishedDate nullable: false
         priceUSD min: 0.00G, scale: 2
         inStock nullable: false
diff --git a/guides/grails-fields-custom-widgets-and-wrappers/v8/snippets/grails-app/init/example/BootStrap.groovy b/guides/grails-fields-custom-widgets-and-wrappers/v8/snippets/grails-app/init/example/BootStrap.groovy
index a4eb6d45fa6..52a33f60c5f 100644
--- a/guides/grails-fields-custom-widgets-and-wrappers/v8/snippets/grails-app/init/example/BootStrap.groovy
+++ b/guides/grails-fields-custom-widgets-and-wrappers/v8/snippets/grails-app/init/example/BootStrap.groovy
@@ -20,9 +20,11 @@ class BootStrap {
                 bio: 'English writer and philologist, best known for The Hobbit and The Lord of the Rings.',
                 website: 'https://www.tolkienestate.com/'
         )
+        tolkien.save(failOnError: true, flush: true)
         tolkien.contactInfo = new ContactInfo(
                 phone: '+44 20 7946 0958',
-                mailingAddress: '1 Oxford Way, Oxford, England'
+                mailingAddress: '1 Oxford Way, Oxford, England',
+                author: tolkien
         )
         tolkien.save(failOnError: true)
 
@@ -32,9 +34,11 @@ class BootStrap {
                 bio: 'English novelist known primarily for her six major novels of the early 19th century.',
                 website: null
         )
+        austen.save(failOnError: true, flush: true)
         austen.contactInfo = new ContactInfo(
                 phone: '+44 1256 462100',
-                mailingAddress: 'Steventon Rectory, Hampshire, England'
+                mailingAddress: 'Steventon Rectory, Hampshire, England',
+                author: austen
         )
         austen.save(failOnError: true)
 
diff --git a/guides/grails-github-actions-cicd/v8/snippets/grails-app/domain/example/Message.groovy b/guides/grails-github-actions-cicd/v8/snippets/grails-app/domain/example/Message.groovy
index 28f0a0d9632..d0cb9ed13ae 100644
--- a/guides/grails-github-actions-cicd/v8/snippets/grails-app/domain/example/Message.groovy
+++ b/guides/grails-github-actions-cicd/v8/snippets/grails-app/domain/example/Message.groovy
@@ -8,7 +8,7 @@ class Message {
     String content
 
     static constraints = {
-        content blank: false, maxSize: 500
+        content nullable: false, blank: false, maxSize: 500
     }
 
     String toString() { content }
diff --git a/guides/grails-htmx/v8/guide/taskDomain.adoc b/guides/grails-htmx/v8/guide/taskDomain.adoc
index af9bc87e819..12d2bf28e95 100644
--- a/guides/grails-htmx/v8/guide/taskDomain.adoc
+++ b/guides/grails-htmx/v8/guide/taskDomain.adoc
@@ -6,7 +6,7 @@ A small Task domain class drives the UI:
 include::../snippets/grails-app/domain/example/Task.groovy[]
 ----
 
-`title blank: false, maxSize: 255` is the only constraint that matters for this guide; we will exercise it in both the create form and the inline edit form. `dateCreated` is a GORM-managed timestamp. `static mapping { sort 'dateCreated'; order 'desc' }` makes new tasks appear at the top of the list without any controller-side sorting code.
+`title nullable: false, blank: false, maxSize: 255` is the only constraint that matters for this guide; we will exercise it in both the create form and the inline edit form. Grails 8 defaults `nullable` to `true`, so `blank: false` alone does not reject a missing title. `dateCreated` is a GORM-managed timestamp. `static mapping { sort 'dateCreated'; order 'desc' }` makes new tasks appear at the top of the list without any controller-side sorting code.
 
 Files touched:
 
diff --git a/guides/grails-htmx/v8/snippets/grails-app/domain/example/Task.groovy b/guides/grails-htmx/v8/snippets/grails-app/domain/example/Task.groovy
index 6add627a26c..c537698a372 100644
--- a/guides/grails-htmx/v8/snippets/grails-app/domain/example/Task.groovy
+++ b/guides/grails-htmx/v8/snippets/grails-app/domain/example/Task.groovy
@@ -10,7 +10,7 @@ class Task {
     Date dateCreated
 
     static constraints = {
-        title blank: false, maxSize: 255
+        title nullable: false, blank: false, maxSize: 255
     }
 
     static mapping = {
diff --git a/guides/grails-jobrunr/v8/guide/gettingStarted.adoc b/guides/grails-jobrunr/v8/guide/gettingStarted.adoc
index 975737db49c..dbc1e6ab517 100644
--- a/guides/grails-jobrunr/v8/guide/gettingStarted.adoc
+++ b/guides/grails-jobrunr/v8/guide/gettingStarted.adoc
@@ -1,6 +1,6 @@
 JobRunr is a durable background-job system: it stores job details, lets workers execute them after a request returns, retries failures, and provides an operational dashboard. This guide builds a small delivery API in which creating a delivery commits a GORM row, then schedules a JobRunr job that marks that delivery complete.
 
-The sample is verified with Grails `8.0.0-M5`, Gradle `9.6.1`, Java 21, and JobRunr OSS `8.8.1`. Grails `8.0.0-M5` is a milestone release, while JobRunr `8.8.1` is a GA release. Check the current https://grails.apache.org[Apache Grails] and https://github.com/jobrunr/jobrunr/releases/tag/v8.8.1[JobRunr 8.8.1 release] before choosing versions for a new production application.
+The sample is verified with Grails `8.0.0`, Gradle `9.8.0`, Java 21, and JobRunr OSS `8.8.1`. Grails `8.0.0` and JobRunr `8.8.1` are GA releases. Check the current https://grails.apache.org[Apache Grails] and https://github.com/jobrunr/jobrunr/releases/tag/v8.8.1[JobRunr 8.8.1 release] before choosing versions for a new production application.
 
 The design deliberately uses JobRunr OSS only. It stores JobRunr tables in a dedicated data source, sends a small delivery ID rather than an entity graph, waits for the delivery transaction to commit before enqueueing, and makes the handler safe to run again.
 
diff --git a/guides/grails-jobrunr/v8/guide/jobRequests.adoc b/guides/grails-jobrunr/v8/guide/jobRequests.adoc
index 138b012e219..836b8870286 100644
--- a/guides/grails-jobrunr/v8/guide/jobRequests.adoc
+++ b/guides/grails-jobrunr/v8/guide/jobRequests.adoc
@@ -7,7 +7,7 @@ JobRunr needs a durable description of the method to execute. An ordinary Groovy
 jobScheduler.enqueue((JobLambda) (() -> processDelivery(deliveryId)))
 ----
 
-The cast chooses a Java functional-interface overload, but it does not produce a javac-style JobRunr target. Executed checks against Groovy `4.0.33` and against Grails `8.0.0-M5` with Groovy `5.0.8` both persisted a generated `Closure#doCall` target. The jobs failed before the intended method ran. Never claim this cast-arrow form is a workaround.
+The cast chooses a Java functional-interface overload, but it does not produce a javac-style JobRunr target. Executed checks against Groovy `4.0.33` and against Grails `8.0.0` with Groovy `5.0.8` both persisted a generated `Closure#doCall` target. The jobs failed before the intended method ran. Never claim this cast-arrow form is a workaround.
 
 Use JobRunr's explicit https://github.com/jobrunr/jobrunr/blob/v8.8.1/core/src/main/java/org/jobrunr/jobs/lambdas/JobRequest.java[`JobRequest`] and https://github.com/jobrunr/jobrunr/blob/v8.8.1/core/src/main/java/org/jobrunr/jobs/lambdas/JobRequestHandler.java[`JobRequestHandler`] contract instead. The request carries only the durable delivery ID, has a no-argument constructor for deserialization, and maps itself to a Spring handler:
 
diff --git a/guides/grails-jobrunr/v8/guide/requirements.adoc b/guides/grails-jobrunr/v8/guide/requirements.adoc
index d1345dccece..26391d0037c 100644
--- a/guides/grails-jobrunr/v8/guide/requirements.adoc
+++ b/guides/grails-jobrunr/v8/guide/requirements.adoc
@@ -3,6 +3,6 @@ To complete this guide, you will need:
 * JDK 21. Grails 8 requires Java 21.
 * About 45 minutes.
 * An IDE with Groovy support.
-* The Gradle wrapper included with the sample. It is pinned to Gradle `9.6.1`.
+* The Gradle wrapper included with the sample. It is pinned to Gradle `9.8.0`.
 
 The sample uses in-memory H2 databases for application and JobRunr storage. That is suitable for learning and tests only. The production section explains the persistent writer database and schema workflow.
diff --git a/guides/grails-jobrunr/v8/snippets/complete/gradle.properties b/guides/grails-jobrunr/v8/snippets/complete/gradle.properties
index 7e8e599c9dd..d323a7c961d 100644
--- a/guides/grails-jobrunr/v8/snippets/complete/gradle.properties
+++ b/guides/grails-jobrunr/v8/snippets/complete/gradle.properties
@@ -1,4 +1,4 @@
-grailsVersion=8.0.0-M5
+grailsVersion=8.0.0
 jobrunrVersion=8.8.1
 org.gradle.caching=true
 org.gradle.jvmargs=-Dfile.encoding=UTF-8 -Xmx1g
diff --git a/guides/grails-jobrunr/v8/snippets/complete/grails-app/domain/example/grails/Delivery.groovy b/guides/grails-jobrunr/v8/snippets/complete/grails-app/domain/example/grails/Delivery.groovy
index fe32c43278f..4593c4f9c6b 100644
--- a/guides/grails-jobrunr/v8/snippets/complete/grails-app/domain/example/grails/Delivery.groovy
+++ b/guides/grails-jobrunr/v8/snippets/complete/grails-app/domain/example/grails/Delivery.groovy
@@ -8,7 +8,7 @@ class Delivery {
 
     static constraints = {
         reference nullable: false, blank: false, unique: true
-        status blank: false
+        status nullable: false, blank: false
         completedAt nullable: true
     }
 }
diff --git a/guides/grails-jobrunr/v8/snippets/initial/gradle.properties b/guides/grails-jobrunr/v8/snippets/initial/gradle.properties
index 3532b99e899..fbf6e62b2b0 100644
--- a/guides/grails-jobrunr/v8/snippets/initial/gradle.properties
+++ b/guides/grails-jobrunr/v8/snippets/initial/gradle.properties
@@ -1,3 +1,3 @@
-grailsVersion=8.0.0-M5
+grailsVersion=8.0.0
 org.gradle.caching=true
 org.gradle.jvmargs=-Dfile.encoding=UTF-8 -Xmx1g
diff --git a/guides/grails-multi-module/v8/guide/failureModes.adoc b/guides/grails-multi-module/v8/guide/failureModes.adoc
index 2dd0c919213..1cccca7521f 100644
--- a/guides/grails-multi-module/v8/guide/failureModes.adoc
+++ b/guides/grails-multi-module/v8/guide/failureModes.adoc
@@ -1,9 +1,11 @@
-Three failure modes show up again and again in real Grails multi-module workspaces.
+Four failure modes show up again and again in real Grails multi-module workspaces.
 
 * *Circular module dependencies, disguised as plugin wiring.* The `dependsOn` and `loadAfter` properties on a Grails plugin descriptor are runtime ordering hints from `grails-core/src/main/groovy/grails/plugins/Plugin.groovy`. They do not replace Gradle project dependencies. If a webapp depends on `shared-core`, then `shared-core` must not depend back on that webapp. Keep the dependency arrow one-way: webapps depend on the plugin.
 
 * *Mapping collisions in the Hibernate model.* Grails 8 collects every domain artefact into the mapping context and Hibernate bootstrap path. You can see that in `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`. If the plugin and a webapp both define separate entities that map to the same table or incompatible assoc [...]
 
+* *A plugin ships `messages.properties`.* Grails 8 rejects a plugin message bundle that is not named for that plugin (`Plugin 'shared-core' ships message bundles outside its own namespace`). Name plugin bundles `shared-core.properties` and `shared-core_<locale>.properties`, not `messages.properties`.
+
 * *Both webapps try to run startup migrations.* `grails-data-hibernate5/dbmigration/src/main/groovy/org/grails/plugins/databasemigration/DatabaseMigrationGrailsPlugin.groovy` runs Liquibase updates at startup when `grails.plugin.databasemigration.updateOnStart*` flags are enabled. Liquibase locking prevents corruption, but it still leaves startup order and deploy timing to chance. Pick one app to own migrations. Let the other app boot against an already-migrated schema.
 
 Files touched:
diff --git a/guides/grails-multi-module/v8/snippets/shared-core/grails-app/domain/example/Book.groovy b/guides/grails-multi-module/v8/snippets/shared-core/grails-app/domain/example/Book.groovy
index a10ffb0a9a4..c50be5cff53 100644
--- a/guides/grails-multi-module/v8/snippets/shared-core/grails-app/domain/example/Book.groovy
+++ b/guides/grails-multi-module/v8/snippets/shared-core/grails-app/domain/example/Book.groovy
@@ -11,8 +11,8 @@ class Book {
     Date    publishedOn
 
     static constraints = {
-        title       blank: false, maxSize: 255
-        isbn        blank: false, unique: true, matches: /^(97(8|9))?\d{9}(\d|X)$/
+        title       nullable: false, blank: false, maxSize: 255
+        isbn        nullable: false, blank: false, unique: true, matches: /^(97(8|9))?\d{9}(\d|X)$/
         pageCount   nullable: true, min: 1
         publishedOn nullable: true
     }
diff --git a/guides/grails-rest-library/v8/guide/domainModel.adoc b/guides/grails-rest-library/v8/guide/domainModel.adoc
index 043289a174a..6b46357ee62 100644
--- a/guides/grails-rest-library/v8/guide/domainModel.adoc
+++ b/guides/grails-rest-library/v8/guide/domainModel.adoc
@@ -14,7 +14,7 @@ include::../snippets/grails-app/domain/example/Book.groovy[]
 
 A few constraint choices to highlight:
 
-* `name blank: false, maxSize: 255` on Author and `title` on Book reject empty strings and cap database column width, so the rejection is surfaced at validation time, not as a SQL error.
+* `name nullable: false, blank: false, maxSize: 255` on Author, and the same pair on Book `title` and `isbn`, reject a missing or empty string at validation time. Grails 8 defaults `nullable` to `true`, and `belongsTo` no longer implies `nullable: false`, so Book also declares `author nullable: false`.
 * `isbn matches: /^(97(8|9))?\d{9}(\d|X)$/` is a soft ISBN-10/13 check. It is intentionally permissive (no checksum validation); the regex is a chapter-and-verse example of how Grails' `matches:` constraint composes with `unique: true`.
 * `pageCount nullable: true, min: 1` allows an unset value but rejects zero or negative integers.
 
diff --git a/guides/grails-rest-library/v8/snippets/grails-app/controllers/example/BookController.groovy b/guides/grails-rest-library/v8/snippets/grails-app/controllers/example/BookController.groovy
index f878cae6dc1..f7fd94f542e 100644
--- a/guides/grails-rest-library/v8/snippets/grails-app/controllers/example/BookController.groovy
+++ b/guides/grails-rest-library/v8/snippets/grails-app/controllers/example/BookController.groovy
@@ -43,15 +43,15 @@ class BookController extends RestfulController<Book> {
     }
 
     @Override
-    protected Integer countResources() {
+    protected Long countResources() {
         Long authorId = params.long('author')
         if (authorId != null) {
             return Book.where {
                 author.id == authorId
-            }.count() as Integer
+            }.count() as Long
         }
 
-        Book.count()
+        Book.count() as Long
     }
 
     @Transactional
@@ -106,6 +106,6 @@ class BookController extends RestfulController<Book> {
 
         params.max = Math.max(drafts.size(), 1)
         params.offset = 0
-        respond drafts, [status: HttpStatus.CREATED, view: 'index', model: [bookCount: drafts.size()]]
+        respond drafts, [status: HttpStatus.CREATED, view: 'index', model: [bookCount: drafts.size() as Long]]
     }
 }
diff --git a/guides/grails-rest-library/v8/snippets/grails-app/domain/example/Author.groovy b/guides/grails-rest-library/v8/snippets/grails-app/domain/example/Author.groovy
index 274de2d48dc..dd7f865fc17 100644
--- a/guides/grails-rest-library/v8/snippets/grails-app/domain/example/Author.groovy
+++ b/guides/grails-rest-library/v8/snippets/grails-app/domain/example/Author.groovy
@@ -12,7 +12,7 @@ class Author {
     static hasMany = [books: Book]
 
     static constraints = {
-        name        blank: false, maxSize: 255
+        name        nullable: false, blank: false, maxSize: 255
         biography   nullable: true, maxSize: 4000
         dateOfBirth nullable: true
     }
diff --git a/guides/grails-rest-library/v8/snippets/grails-app/domain/example/Book.groovy b/guides/grails-rest-library/v8/snippets/grails-app/domain/example/Book.groovy
index 7c1da607b48..71b63d6a7d1 100644
--- a/guides/grails-rest-library/v8/snippets/grails-app/domain/example/Book.groovy
+++ b/guides/grails-rest-library/v8/snippets/grails-app/domain/example/Book.groovy
@@ -13,8 +13,9 @@ class Book {
     static belongsTo = [author: Author]
 
     static constraints = {
-        title       blank: false, maxSize: 255
-        isbn        blank: false, unique: true, maxSize: 20, matches: /^(97(8|9))?\d{9}(\d|X)$/
+        author      nullable: false
+        title       nullable: false, blank: false, maxSize: 255
+        isbn        nullable: false, blank: false, unique: true, maxSize: 20, matches: /^(97(8|9))?\d{9}(\d|X)$/
         pageCount   nullable: true, min: 1
         publishedOn nullable: true
     }
diff --git a/guides/grails-rest-library/v8/snippets/grails-app/views/author/index.gson b/guides/grails-rest-library/v8/snippets/grails-app/views/author/index.gson
index 9f1bd246a82..b3020971623 100644
--- a/guides/grails-rest-library/v8/snippets/grails-app/views/author/index.gson
+++ b/guides/grails-rest-library/v8/snippets/grails-app/views/author/index.gson
@@ -2,7 +2,7 @@ import example.Author
 
 model {
     Iterable<Author> authorList
-    Integer          authorCount
+    Long             authorCount
 }
 
 json {
diff --git a/guides/grails-rest-library/v8/snippets/grails-app/views/book/index.gson b/guides/grails-rest-library/v8/snippets/grails-app/views/book/index.gson
index 1b478abfc88..dfc9bf051c0 100644
--- a/guides/grails-rest-library/v8/snippets/grails-app/views/book/index.gson
+++ b/guides/grails-rest-library/v8/snippets/grails-app/views/book/index.gson
@@ -2,7 +2,7 @@ import example.Book
 
 model {
     Iterable<Book> bookList
-    Integer        bookCount
+    Long           bookCount
 }
 
 json {
diff --git a/guides/grails-spock-test-tour/v8/snippets/grails-app/domain/example/Book.groovy b/guides/grails-spock-test-tour/v8/snippets/grails-app/domain/example/Book.groovy
index 1fc72a2eefb..8cf2b331299 100644
--- a/guides/grails-spock-test-tour/v8/snippets/grails-app/domain/example/Book.groovy
+++ b/guides/grails-spock-test-tour/v8/snippets/grails-app/domain/example/Book.groovy
@@ -10,8 +10,8 @@ class Book {
     Integer pageCount
 
     static constraints = {
-        title     blank: false, maxSize: 255
-        isbn      blank: false, unique: true, matches: /^(97(8|9))?\d{9}(\d|X)$/
+        title     nullable: false, blank: false, maxSize: 255
+        isbn      nullable: false, blank: false, unique: true, matches: /^(97(8|9))?\d{9}(\d|X)$/
         pageCount nullable: true, min: 1
     }
 }
diff --git a/guides/grails-spring-security/v8/snippets/build.gradle b/guides/grails-spring-security/v8/snippets/build.gradle
index 09d35e63e88..ef245f85a39 100644
--- a/guides/grails-spring-security/v8/snippets/build.gradle
+++ b/guides/grails-spring-security/v8/snippets/build.gradle
@@ -1 +1 @@
-    implementation "org.apache.grails:grails-spring-security:8.0.0-SNAPSHOT"
+    implementation "org.apache.grails:grails-spring-security:8.0.0"
diff --git a/guides/grails-spring-security/v8/snippets/grails-app/domain/example/User.groovy b/guides/grails-spring-security/v8/snippets/grails-app/domain/example/User.groovy
index 68a9cd1d3ea..1aa94f825b9 100644
--- a/guides/grails-spring-security/v8/snippets/grails-app/domain/example/User.groovy
+++ b/guides/grails-spring-security/v8/snippets/grails-app/domain/example/User.groovy
@@ -25,7 +25,7 @@ class User implements Serializable {
     }
 
     static constraints = {
-        password blank: false, password: true
+        password nullable: false, blank: false, password: true
         username blank: false, nullable: false, unique: true
     }
 
diff --git a/guides/grails-tailwindcss/v8/snippets/build.gradle b/guides/grails-tailwindcss/v8/snippets/build.gradle
index e6077b52f01..c74e52d431b 100644
--- a/guides/grails-tailwindcss/v8/snippets/build.gradle
+++ b/guides/grails-tailwindcss/v8/snippets/build.gradle
@@ -109,7 +109,6 @@ dependencies {
     runtimeOnly "cloud.wondrify:asset-pipeline-grails"
     runtimeOnly "com.h2database:h2"
     runtimeOnly "com.zaxxer:HikariCP"
-    runtimeOnly "org.fusesource.jansi:jansi"
     testAndDevelopmentOnly "org.webjars.npm:bootstrap"
     testAndDevelopmentOnly "org.webjars.npm:bootstrap-icons"
     testAndDevelopmentOnly "org.webjars.npm:jquery"
diff --git a/guides/grails-transactional-events/v8/guide/testingThePattern.adoc b/guides/grails-transactional-events/v8/guide/testingThePattern.adoc
index d9b423fd172..e4dad849a26 100644
--- a/guides/grails-transactional-events/v8/guide/testingThePattern.adoc
+++ b/guides/grails-transactional-events/v8/guide/testingThePattern.adoc
@@ -10,7 +10,7 @@ Plain Spock specs with `DomainUnitTest` / `DataTest` verify every constraint, an
 include::../snippets/src/test/groovy/example/CustomerRequestStatusSpec.groovy[]
 ----
 
-The sample repo carries one spec per domain (`CustomerSpec`, `OrderSpec`, `AuditLogSpec`, `CustomerRequestSpec`, `WorkOrderSpec`, `WorkOrderAssigneeSpec`). One worth calling out: a blank string set through the *map constructor* is converted to `null` by Grails data binding, so a `blank: false` field reports a `nullable` error rather than `blank`. To test the `blank` constraint itself, assign the property directly.
+The sample repo carries one spec per domain (`CustomerSpec`, `OrderSpec`, `AuditLogSpec`, `CustomerRequestSpec`, `WorkOrderSpec`, `WorkOrderAssigneeSpec`). Grails 8 defaults `nullable` to `true`, including associations, so required strings and associations in this sample declare `nullable: false` explicitly. A blank string set through the *map constructor* is converted to `null` by Grails data binding, so a `blank: false` field reports a `nullable` error rather than `blank`. To test the  [...]
 
 == Integration: the transactional contract
 
diff --git a/guides/grails-transactional-events/v8/snippets/grails-app/domain/example/AuditLog.groovy b/guides/grails-transactional-events/v8/snippets/grails-app/domain/example/AuditLog.groovy
index f3addf36c8b..a882c070296 100644
--- a/guides/grails-transactional-events/v8/snippets/grails-app/domain/example/AuditLog.groovy
+++ b/guides/grails-transactional-events/v8/snippets/grails-app/domain/example/AuditLog.groovy
@@ -12,7 +12,7 @@ class AuditLog {
     Date occurredAt
 
     static constraints = {
-        eventType  blank: false, maxSize: 64
+        eventType  nullable: false, blank: false, maxSize: 64
         orderId    min: 1L
         customerId min: 1L
         orderTotal min: new BigDecimal('0.01')
diff --git a/guides/grails-transactional-events/v8/snippets/grails-app/domain/example/Customer.groovy b/guides/grails-transactional-events/v8/snippets/grails-app/domain/example/Customer.groovy
index 445ea78efd0..98b22d7198d 100644
--- a/guides/grails-transactional-events/v8/snippets/grails-app/domain/example/Customer.groovy
+++ b/guides/grails-transactional-events/v8/snippets/grails-app/domain/example/Customer.groovy
@@ -10,7 +10,7 @@ class Customer {
     BigDecimal lifetimeValue = BigDecimal.ZERO
 
     static constraints = {
-        name          blank: false, maxSize: 255
+        name          nullable: false, blank: false, maxSize: 255
         email         email: true, unique: true, maxSize: 255
         lifetimeValue min: BigDecimal.ZERO
     }
diff --git a/guides/grails-transactional-events/v8/snippets/grails-app/domain/example/CustomerRequest.groovy b/guides/grails-transactional-events/v8/snippets/grails-app/domain/example/CustomerRequest.groovy
index 5e33fc22f09..a2647ab322b 100644
--- a/guides/grails-transactional-events/v8/snippets/grails-app/domain/example/CustomerRequest.groovy
+++ b/guides/grails-transactional-events/v8/snippets/grails-app/domain/example/CustomerRequest.groovy
@@ -14,7 +14,7 @@ class CustomerRequest {
     CustomerRequestStatus status = CustomerRequestStatus.SUBMITTED
 
     static constraints = {
-        summary blank: false, maxSize: 255
+        summary nullable: false, blank: false, maxSize: 255
         status  nullable: false
     }
 
diff --git a/guides/grails-transactional-events/v8/snippets/grails-app/domain/example/Order.groovy b/guides/grails-transactional-events/v8/snippets/grails-app/domain/example/Order.groovy
index 4bad44b946d..c15b900b095 100644
--- a/guides/grails-transactional-events/v8/snippets/grails-app/domain/example/Order.groovy
+++ b/guides/grails-transactional-events/v8/snippets/grails-app/domain/example/Order.groovy
@@ -12,7 +12,8 @@ class Order {
     static belongsTo = [customer: Customer]
 
     static constraints = {
-        total min: new BigDecimal('0.01')
+        customer nullable: false
+        total nullable: false, min: new BigDecimal('0.01')
     }
 
     static mapping = {
diff --git a/guides/grails-transactional-events/v8/snippets/grails-app/domain/example/WorkOrder.groovy b/guides/grails-transactional-events/v8/snippets/grails-app/domain/example/WorkOrder.groovy
index e00d3cd8a7c..c755a4aacc9 100644
--- a/guides/grails-transactional-events/v8/snippets/grails-app/domain/example/WorkOrder.groovy
+++ b/guides/grails-transactional-events/v8/snippets/grails-app/domain/example/WorkOrder.groovy
@@ -18,7 +18,8 @@ class WorkOrder {
     static belongsTo = [customerRequest: CustomerRequest]
 
     static constraints = {
-        description blank: false, maxSize: 255
+        customerRequest nullable: false
+        description nullable: false, blank: false, maxSize: 255
         status      nullable: false
     }
 
diff --git a/guides/grails-transactional-events/v8/snippets/grails-app/domain/example/WorkOrderAssignee.groovy b/guides/grails-transactional-events/v8/snippets/grails-app/domain/example/WorkOrderAssignee.groovy
index cfbcb8d418a..92d6b4191d0 100644
--- a/guides/grails-transactional-events/v8/snippets/grails-app/domain/example/WorkOrderAssignee.groovy
+++ b/guides/grails-transactional-events/v8/snippets/grails-app/domain/example/WorkOrderAssignee.groovy
@@ -18,6 +18,7 @@ class WorkOrderAssignee {
     static belongsTo = [workOrder: WorkOrder]
 
     static constraints = {
-        employeeName blank: false, maxSize: 255
+        workOrder nullable: false
+        employeeName nullable: false, blank: false, maxSize: 255
     }
 }
diff --git a/guides/grails-vite-spa/v8/snippets/backend/grails-app/domain/example/Book.groovy b/guides/grails-vite-spa/v8/snippets/backend/grails-app/domain/example/Book.groovy
index f96d1e5080b..6428678afb9 100644
--- a/guides/grails-vite-spa/v8/snippets/backend/grails-app/domain/example/Book.groovy
+++ b/guides/grails-vite-spa/v8/snippets/backend/grails-app/domain/example/Book.groovy
@@ -10,8 +10,8 @@ class Book {
     String  isbn
 
     static constraints = {
-        title  blank: false, maxSize: 255
-        author blank: false, maxSize: 255
-        isbn   blank: false, unique: true, matches: /^(97(8|9))?\d{9}(\d|X)$/
+        title  nullable: false, blank: false, maxSize: 255
+        author nullable: false, blank: false, maxSize: 255
+        isbn   nullable: false, blank: false, unique: true, matches: /^(97(8|9))?\d{9}(\d|X)$/
     }
 }
diff --git a/guides/grails-vite-spa/v8/snippets/backend/grails-app/views/book/index.gson b/guides/grails-vite-spa/v8/snippets/backend/grails-app/views/book/index.gson
index a16a21918ab..9cf8db10f1b 100644
--- a/guides/grails-vite-spa/v8/snippets/backend/grails-app/views/book/index.gson
+++ b/guides/grails-vite-spa/v8/snippets/backend/grails-app/views/book/index.gson
@@ -2,7 +2,7 @@ import example.Book
 
 model {
     Iterable<Book> bookList
-    Integer        bookCount
+    Long           bookCount
 }
 
 json {