diff --git a/grails-doc/src/en/guide/reference.adoc b/grails-doc/src/en/guide/reference.adoc
index 7597ccb630d..e69395e216a 100644
--- a/grails-doc/src/en/guide/reference.adoc
+++ b/grails-doc/src/en/guide/reference.adoc
@@ -1138,6 +1138,11 @@ include::ref/Tags/fieldValue.adoc[]
include::ref/Tags/findAll.adoc[]
+[[ref-tags-flashMessages]]
+==== flashMessages
+
+include::ref/Tags/flashMessages.adoc[]
+
[[ref-tags-form]]
==== form
diff --git a/grails-doc/src/en/guide/upgrading/upgrading71x.adoc b/grails-doc/src/en/guide/upgrading/upgrading71x.adoc
index a8511a90435..0ec9b20439f 100644
--- a/grails-doc/src/en/guide/upgrading/upgrading71x.adoc
+++ b/grails-doc/src/en/guide/upgrading/upgrading71x.adoc
@@ -721,4 +721,25 @@ If this causes issues with existing URL mappings, you can disable it in `applica
grails:
urlmapping:
validateWildcards: false
-----
\ No newline at end of file
+----
+
+===== 2.10 Flash Messages Tag (`g:flashMessages`)
+
+Grails 7.1 introduces a new `` tag that renders `flash.message`, `flash.error`, and `flash.warning` as Bootstrap 5 dismissible alerts with appropriate styling (success, danger, and warning respectively).
+
+The tag automatically prevents duplicate rendering -- if called in both a page and its layout, only the first invocation produces output. All flash content is HTML-encoded to prevent XSS.
+
+The default layout (`main.gsp`) and all scaffolding templates now use this tag. If your application has custom views that render flash messages manually, you can replace the boilerplate:
+
+[source,xml]
+----
+<%-- Before --%>
+
+
${flash.message}
+
+
+<%-- After --%>
+
+----
+
+The tag supports optional attributes for customizing CSS classes, icons, ARIA role, and dismissibility. See the {gspTagsRef}flashMessages.html[flashMessages] tag reference for full details.
diff --git a/grails-doc/src/en/ref/Tags - GSP/flashMessages.adoc b/grails-doc/src/en/ref/Tags - GSP/flashMessages.adoc
new file mode 100644
index 00000000000..a469654a0b8
--- /dev/null
+++ b/grails-doc/src/en/ref/Tags - GSP/flashMessages.adoc
@@ -0,0 +1,120 @@
+////
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+https://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+////
+
+
+== flashMessages
+
+
+
+=== Purpose
+
+
+Renders `flash.message`, `flash.error`, and `flash.warning` as dismissible Bootstrap alert divs with appropriate styling. Automatically prevents duplicate rendering when used in both pages and layouts.
+
+
+=== Examples
+
+
+Basic usage in a view:
+
+[source,xml]
+----
+
+----
+
+In a layout (safe to use alongside page-level usage -- the tag skips rendering if already called):
+
+[source,xml]
+----
+
+
+----
+
+Non-dismissible alerts:
+
+[source,xml]
+----
+
+----
+
+Custom ARIA role:
+
+[source,xml]
+----
+
+----
+
+Custom styling for success messages:
+
+[source,xml]
+----
+
+----
+
+Setting flash messages in a controller:
+
+[source,groovy]
+----
+// Success message (renders as green alert)
+flash.message = "Book '${book.title}' saved successfully."
+
+// Error message (renders as red alert)
+flash.error = "Unable to delete the record."
+
+// Warning message (renders as yellow alert)
+flash.warning = "You have unsaved changes."
+----
+
+
+=== Description
+
+
+The `flashMessages` tag renders any combination of `flash.message`, `flash.error`, and `flash.warning` as Bootstrap 5 alert divs. Each flash key maps to a different alert style:
+
+[cols="1,2,2"]
+|===
+| Flash Key | Default Alert Class | Default Icon
+
+| `flash.message`
+| `alert alert-success alert-dismissible fade show`
+| `bi bi-check-circle me-2`
+
+| `flash.error`
+| `alert alert-danger alert-dismissible fade show`
+| `bi bi-exclamation-triangle me-2`
+
+| `flash.warning`
+| `alert alert-warning alert-dismissible fade show`
+| `bi bi-exclamation-circle me-2`
+|===
+
+The tag automatically sets a `_flashRendered` request attribute after rendering. On subsequent calls within the same request, the tag detects this attribute and outputs nothing. This allows both pages and layouts to include `` without producing duplicate alerts -- whichever renders first wins.
+
+All flash message content is HTML-encoded to prevent XSS.
+
+Attributes
+
+* `messageClass` (optional) - CSS class for `flash.message` alerts. Default: `alert alert-success alert-dismissible fade show`
+* `messageIcon` (optional) - Icon class for `flash.message` alerts. Default: `bi bi-check-circle me-2`
+* `errorClass` (optional) - CSS class for `flash.error` alerts. Default: `alert alert-danger alert-dismissible fade show`
+* `errorIcon` (optional) - Icon class for `flash.error` alerts. Default: `bi bi-exclamation-triangle me-2`
+* `warningClass` (optional) - CSS class for `flash.warning` alerts. Default: `alert alert-warning alert-dismissible fade show`
+* `warningIcon` (optional) - Icon class for `flash.warning` alerts. Default: `bi bi-exclamation-circle me-2`
+* `role` (optional) - ARIA role for alert divs. Default: `alert`
+* `dismissible` (optional) - Whether to show a close button. Default: `true`
diff --git a/grails-forge/grails-forge-core/src/main/resources/gsp/main.gsp b/grails-forge/grails-forge-core/src/main/resources/gsp/main.gsp
index 96309fbd74c..214d932b782 100644
--- a/grails-forge/grails-forge-core/src/main/resources/gsp/main.gsp
+++ b/grails-forge/grails-forge-core/src/main/resources/gsp/main.gsp
@@ -21,6 +21,7 @@
+
diff --git a/grails-gsp/plugin/src/main/groovy/org/grails/plugins/web/taglib/ApplicationTagLib.groovy b/grails-gsp/plugin/src/main/groovy/org/grails/plugins/web/taglib/ApplicationTagLib.groovy
index bd14789f672..69699217d9e 100644
--- a/grails-gsp/plugin/src/main/groovy/org/grails/plugins/web/taglib/ApplicationTagLib.groovy
+++ b/grails-gsp/plugin/src/main/groovy/org/grails/plugins/web/taglib/ApplicationTagLib.groovy
@@ -471,4 +471,68 @@ class ApplicationTagLib implements ApplicationContextAware, InitializingBean, Gr
// encoding is handled in GroovyPage.invokeTag and GroovyPage.captureTagOutput
body()
}
+
+ /**
+ * Renders flash.message, flash.error, and flash.warning as Bootstrap alert divs.
+ * Automatically skips rendering if already called during this request, preventing
+ * duplicate display when used in both pages and layouts.
+ *
+ * @emptyTag
+ *
+ * @attr messageClass CSS class for flash.message alerts (default: 'alert alert-success alert-dismissible fade show')
+ * @attr messageIcon Icon class for flash.message alerts (default: 'bi bi-check-circle me-2')
+ * @attr errorClass CSS class for flash.error alerts (default: 'alert alert-danger alert-dismissible fade show')
+ * @attr errorIcon Icon class for flash.error alerts (default: 'bi bi-exclamation-triangle me-2')
+ * @attr warningClass CSS class for flash.warning alerts (default: 'alert alert-warning alert-dismissible fade show')
+ * @attr warningIcon Icon class for flash.warning alerts (default: 'bi bi-exclamation-circle me-2')
+ * @attr role ARIA role for alert divs (default: 'alert')
+ * @attr dismissible Whether to show a close button (default: true)
+ */
+ Closure flashMessages = { attrs ->
+ if (request.getAttribute('_flashRendered')) {
+ return
+ }
+
+ boolean rendered = false
+ boolean dismissible = attrs.dismissible != null ? attrs.dismissible.toString().toBoolean() : true
+ String role = attrs.role ?: 'alert'
+
+ if (flash.message) {
+ renderFlashAlert(
+ attrs.messageClass ?: 'alert alert-success alert-dismissible fade show',
+ attrs.messageIcon ?: 'bi bi-check-circle me-2',
+ flash.message, dismissible, role)
+ rendered = true
+ }
+
+ if (flash.error) {
+ renderFlashAlert(
+ attrs.errorClass ?: 'alert alert-danger alert-dismissible fade show',
+ attrs.errorIcon ?: 'bi bi-exclamation-triangle me-2',
+ flash.error, dismissible, role)
+ rendered = true
+ }
+
+ if (flash.warning) {
+ renderFlashAlert(
+ attrs.warningClass ?: 'alert alert-warning alert-dismissible fade show',
+ attrs.warningIcon ?: 'bi bi-exclamation-circle me-2',
+ flash.warning, dismissible, role)
+ rendered = true
+ }
+
+ if (rendered) {
+ request.setAttribute('_flashRendered', true)
+ }
+ }
+
+ private void renderFlashAlert(String cssClass, String icon, Object message, boolean dismissible, String role) {
+ out << "
"
+ out << ""
+ out << message.toString().encodeAsHTML()
+ if (dismissible) {
+ out << ''
+ }
+ out << '