Repository: quarkusio/quarkus
Issue: Quarkus 3.2: Hibernate ORM configuration problem

### Describe the bug

I'm upgrading to Quarkus 3.2 from 2.x (lates) and I need to add one new config property:

quarkus.hibernate-orm.mydatasource.mapping.timezone.default-storage=normalize

When I try to build the app with Maven package goal I'm gettting this error:

SRCFG00014: The config property quarkus.hibernate-orm.mydatasource.mapping.query.query-plan-cache-max-size is required but it could not be found in any config source

### Expected behavior

The reported configuration property is both bad (there is no "mapping" prefix") and not mandatory, so it shouldn't complain about it.

### Actual behavior

Halts the build with the mentioned error message.

### How to Reproduce?

_No response_

### Output of `uname -a` or `ver`

_No response_

### Output of `java -version`

17

### GraalVM version (if different from Java)

_No response_

### Quarkus version or git rev

3.2.0

### Build tool (ie. output of `mvnw --version` or `gradlew --version`)

Maven 3.8

### Additional information

_No response_

Changed files:
extensions/hibernate-reactive/deployment/src/main/java/io/quarkus/hibernate/reactive/deployment/HibernateReactiveProcessor.java
extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmConfigPersistenceUnit.java
extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java

diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmConfigPersistenceUnit.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmConfigPersistenceUnit.java
index 3959bb5f707..7462c4b1308 100644
--- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmConfigPersistenceUnit.java
+++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmConfigPersistenceUnit.java
@@ -341,71 +341,98 @@ public boolean isAnyPropertySet() {
     @ConfigGroup
     public static class HibernateOrmConfigPersistenceUnitMapping {
         /**
-         * How to store timezones in the database by default
-         * for properties of type `OffsetDateTime` and `ZonedDateTime`.
-         *
-         * This default may be overridden on a per-property basis using `@TimeZoneStorage`.
-         *
-         * NOTE: Properties of type `OffsetTime` are https://hibernate.atlassian.net/browse/HHH-16287[not affected by this
-         * setting].
-         *
-         * `default`::
-         * Equivalent to `native` if supported, `normalize-utc` otherwise.
-         * `auto`::
-         * Equivalent to `native` if supported, `column` otherwise.
-         * `native`::
-         * Stores the timestamp and timezone in a column of type `timestamp with time zone`.
-         * +
-         * Only available on some databases/dialects;
-         * if not supported, an exception will be thrown during static initialization.
-         * `column`::
-         * Stores the timezone in a separate column next to the timestamp column.
-         * +
-         * Use `@TimeZoneColumn` on the relevant entity property to customize the timezone column.
-         * `normalize-utc`::
-         * Does not store the timezone, and loses timezone information upon persisting.
-         * +
-         * Instead, normalizes the value to a timestamp in the UTC timezone.
-         * `normalize`::
-         * Does not store the timezone, and loses timezone information upon persisting.
-         * +
-         * Instead, normalizes the value:
-         * * upon persisting to the database, to a timestamp in the JDBC timezone
-         * set through `quarkus.hibernate-orm.jdbc.timezone`,
-         * or the JVM default timezone if not set.
-         * * upon reading back from the database, to the JVM default timezone.
-         * +
-         * Use this to get the legacy behavior of Quarkus 2 / Hibernate ORM 5 or older.
-         *
-         * @asciidoclet
+         * Timezone configuration.
          */
-        @ConfigItem(name = "timezone.default-storage", defaultValueDocumentation = "default")
-        public Optional<TimeZoneStorageType> timeZoneDefaultStorage;
+        @ConfigItem
+        public Timezone timezone;
 
         /**
-         * The optimizer to apply to identifier generators
-         * whose optimizer is not configured explicitly.
-         *
-         * Only relevant for table- and sequence-based identifier generators.
-         * Other generators, such as UUID-based generators, will ignore this setting.
-         *
-         * The optimizer is responsible for pooling new identifier values,
-         * in order to reduce the frequency of database calls to retrieve those values
-         * and thereby improve performance.
-         *
-         * @asciidoclet
+         * Optimizer configuration.
          */
-        @ConfigItem(name = "id.optimizer.default", defaultValueDocumentation = "pooled-lo")
-        // Note this needs to be a build-time property due to
-        // org.hibernate.boot.internal.InFlightMetadataCollectorImpl.handleIdentifierValueBinding
-        // which may call (indirectly) org.hibernate.id.enhanced.SequenceStructure.buildSequence
-        // whose output depends on org.hibernate.id.enhanced.SequenceStructure.applyIncrementSizeToSourceValues
-        // which is determined by the optimizer.
-        public Optional<IdOptimizerType> idOptimizerDefault;
+        @ConfigItem
+        public Id id;
+
+        @ConfigGroup
+        public static class Timezone {
+            /**
+             * How to store timezones in the database by default
+             * for properties of type `OffsetDateTime` and `ZonedDateTime`.
+             *
+             * This default may be overridden on a per-property basis using `@TimeZoneStorage`.
+             *
+             * NOTE: Properties of type `OffsetTime` are https://hibernate.atlassian.net/browse/HHH-16287[not affected by this
+             * setting].
+             *
+             * `default`::
+             * Equivalent to `native` if supported, `normalize-utc` otherwise.
+             * `auto`::
+             * Equivalent to `native` if supported, `column` otherwise.
+             * `native`::
+             * Stores the timestamp and timezone in a column of type `timestamp with time zone`.
+             * +
+             * Only available on some databases/dialects;
+             * if not supported, an exception will be thrown during static initialization.
+             * `column`::
+             * Stores the timezone in a separate column next to the timestamp column.
+             * +
+             * Use `@TimeZoneColumn` on the relevant entity property to customize the timezone column.
+             * `normalize-utc`::
+             * Does not store the timezone, and loses timezone information upon persisting.
+             * +
+             * Instead, normalizes the value to a timestamp in the UTC timezone.
+             * `normalize`::
+             * Does not store the timezone, and loses timezone information upon persisting.
+             * +
+             * Instead, normalizes the value:
+             * * upon persisting to the database, to a timestamp in the JDBC timezone
+             * set through `quarkus.hibernate-orm.jdbc.timezone`,
+             * or the JVM default timezone if not set.
+             * * upon reading back from the database, to the JVM default timezone.
+             * +
+             * Use this to get the legacy behavior of Quarkus 2 / Hibernate ORM 5 or older.
+             *
+             * @asciidoclet
+             */
+            @ConfigItem(name = "default-storage", defaultValueDocumentation = "default")
+            public Optional<TimeZoneStorageType> timeZoneDefaultStorage;
+        }
+
+        @ConfigGroup
+        public static class Id {
+            /**
+             * Optimizer configuration.
+             */
+            @ConfigItem
+            public Optimizer optimizer;
+
+            @ConfigGroup
+            public static class Optimizer {
+                /**
+                 * The optimizer to apply to identifier generators
+                 * whose optimizer is not configured explicitly.
+                 *
+                 * Only relevant for table- and sequence-based identifier generators.
+                 * Other generators, such as UUID-based generators, will ignore this setting.
+                 *
+                 * The optimizer is responsible for pooling new identifier values,
+                 * in order to reduce the frequency of database calls to retrieve those values
+                 * and thereby improve performance.
+                 *
+                 * @asciidoclet
+                 */
+                @ConfigItem(name = "default", defaultValueDocumentation = "pooled-lo")
+                // Note this needs to be a build-time property due to
+                // org.hibernate.boot.internal.InFlightMetadataCollectorImpl.handleIdentifierValueBinding
+                // which may call (indirectly) org.hibernate.id.enhanced.SequenceStructure.buildSequence
+                // whose output depends on org.hibernate.id.enhanced.SequenceStructure.applyIncrementSizeToSourceValues
+                // which is determined by the optimizer.
+                public Optional<IdOptimizerType> idOptimizerDefault;
+            }
+        }
 
         public boolean isAnyPropertySet() {
-            return timeZoneDefaultStorage.isPresent()
-                    || idOptimizerDefault.isPresent();
+            return timezone.timeZoneDefaultStorage.isPresent()
+                    || id.optimizer.idOptimizerDefault.isPresent();
         }
 
     }
diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java
index 953370f0512..ed82729ff90 100644
--- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java
+++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateOrmProcessor.java
@@ -953,12 +953,12 @@ private static void producePersistenceUnitDescriptorFromConfig(
                         .setProperty(EntityManagerFactoryBuilderImpl.METADATA_BUILDER_CONTRIBUTOR, className));
 
         // Mapping
-        if (persistenceUnitConfig.mapping.timeZoneDefaultStorage.isPresent()) {
+        if (persistenceUnitConfig.mapping.timezone.timeZoneDefaultStorage.isPresent()) {
             descriptor.getProperties().setProperty(AvailableSettings.TIMEZONE_DEFAULT_STORAGE,
-                    persistenceUnitConfig.mapping.timeZoneDefaultStorage.get().name());
+                    persistenceUnitConfig.mapping.timezone.timeZoneDefaultStorage.get().name());
         }
         descriptor.getProperties().setProperty(AvailableSettings.PREFERRED_POOLED_OPTIMIZER,
-                persistenceUnitConfig.mapping.idOptimizerDefault
+                persistenceUnitConfig.mapping.id.optimizer.idOptimizerDefault
                         .orElse(HibernateOrmConfigPersistenceUnit.IdOptimizerType.POOLED_LO).configName);
 
         //charset
diff --git a/extensions/hibernate-reactive/deployment/src/main/java/io/quarkus/hibernate/reactive/deployment/HibernateReactiveProcessor.java b/extensions/hibernate-reactive/deployment/src/main/java/io/quarkus/hibernate/reactive/deployment/HibernateReactiveProcessor.java
index a9270d50321..72f7ce71a52 100644
--- a/extensions/hibernate-reactive/deployment/src/main/java/io/quarkus/hibernate/reactive/deployment/HibernateReactiveProcessor.java
+++ b/extensions/hibernate-reactive/deployment/src/main/java/io/quarkus/hibernate/reactive/deployment/HibernateReactiveProcessor.java
@@ -286,12 +286,12 @@ private static ParsedPersistenceXmlDescriptor generateReactivePersistenceUnit(
                         .setProperty(AvailableSettings.IMPLICIT_NAMING_STRATEGY, namingStrategy));
 
         // Mapping
-        if (persistenceUnitConfig.mapping.timeZoneDefaultStorage.isPresent()) {
+        if (persistenceUnitConfig.mapping.timezone.timeZoneDefaultStorage.isPresent()) {
             desc.getProperties().setProperty(AvailableSettings.TIMEZONE_DEFAULT_STORAGE,
-                    persistenceUnitConfig.mapping.timeZoneDefaultStorage.get().name());
+                    persistenceUnitConfig.mapping.timezone.timeZoneDefaultStorage.get().name());
         }
         desc.getProperties().setProperty(AvailableSettings.PREFERRED_POOLED_OPTIMIZER,
-                persistenceUnitConfig.mapping.idOptimizerDefault
+                persistenceUnitConfig.mapping.id.optimizer.idOptimizerDefault
                         .orElse(HibernateOrmConfigPersistenceUnit.IdOptimizerType.POOLED_LO).configName);
 
         //charset