{"record":{"id":"3fa456745c1bc126","repo":"brettwooldridge/HikariCP","slug":"the-configuration-of-the-pool-is-sealed-once-start","errorCode":null,"errorMessage":"The configuration of the pool is sealed once started. Use HikariConfigMXBean for runtime changes.","messagePattern":"The configuration of the pool is sealed once started\\. Use HikariConfigMXBean for runtime changes\\.","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"src/main/java/com/zaxxer/hikari/HikariConfig.java","lineNumber":1156,"sourceCode":"         minIdle = maxPoolSize;\n      }\n\n      if (idleTimeout + SECONDS.toMillis(1) > maxLifetime && maxLifetime > 0 && minIdle < maxPoolSize) {\n         LOGGER.warn(\"{} - idleTimeout is close to or more than maxLifetime, disabling it.\", poolName);\n         idleTimeout = 0;\n      }\n      else if (idleTimeout != 0 && idleTimeout < SECONDS.toMillis(10) && minIdle < maxPoolSize) {\n         LOGGER.warn(\"{} - idleTimeout is less than 10000ms, setting to default {}ms.\", poolName, IDLE_TIMEOUT);\n         idleTimeout = IDLE_TIMEOUT;\n      }\n      else  if (idleTimeout != IDLE_TIMEOUT && idleTimeout != 0 && minIdle == maxPoolSize) {\n         LOGGER.warn(\"{} - idleTimeout has been set but has no effect because the pool is operating as a fixed size pool.\", poolName);\n      }\n   }\n\n   private void checkIfSealed()\n   {\n      if (sealed) throw new IllegalStateException(\"The configuration of the pool is sealed once started. Use HikariConfigMXBean for runtime changes.\");\n   }\n\n   private void logConfiguration()\n   {\n      LOGGER.debug(\"{} - configuration:\", poolName);\n      final var propertyNames = new TreeSet<>(PropertyElf.getPropertyNames(HikariConfig.class));\n      for (var prop : propertyNames) {\n         try {\n            var value = PropertyElf.getProperty(prop, this);\n            if (\"dataSourceProperties\".equals(prop)) {\n               var dsProps = PropertyElf.copyProperties(dataSourceProperties);\n               dsProps.setProperty(\"password\", \"<masked>\");\n               value = dsProps;\n            }\n\n            if (\"initializationFailTimeout\".equals(prop) && initializationFailTimeout == Long.MAX_VALUE) {\n               value = \"infinite\";\n            }","sourceCodeStart":1138,"sourceCodeEnd":1174,"githubUrl":"https://github.com/brettwooldridge/HikariCP/blob/a4d93f4f85517f90e632b795486d7102e933d7ff/src/main/java/com/zaxxer/hikari/HikariConfig.java#L1138-L1174","documentation":"Once a HikariDataSource/HikariPool has started, HikariConfig is sealed and mutating setters that call checkIfSealed() (setJdbcUrl, setDriverClassName, setHealthCheckRegistry, setCredentialsProviderClassName, setExceptionOverrideClassName, etc.) throw IllegalStateException telling you to use HikariConfigMXBean for runtime changes. This prevents mutating a live pool's identity/behavior fields behind its back; only specific tunables (pool size, timeouts via the MXBean) are runtime-changeable.","triggerScenarios":"Calling setters like setDriverClassName/setJdbcUrl/setDataSourceProperties on a HikariDataSource after it was started — commonly by reusing a started config object for a second pool, Spring's property binding applying to an already-initialized DataSource, or test code mutating a shared singleton pool's config between tests.","commonSituations":"@Autowired HikariDataSource and then calling its setters at runtime; test suites that reuse one static DataSource and reconfigure per test; frameworks that bind properties to an existing bean; mistakenly treating HikariConfig as a mutable runtime handle.","solutions":["Create a new HikariConfig/HikariDataSource for the new settings instead of mutating a started one — copyStateTo(HikariConfig) exists for cloning","For legitimately runtime-tunable values (maximumPoolSize, idleTimeout, maxLifetime, connectionTimeout, minimumIdle), cast to HikariConfigMXBean and call its setters","Restructure tests to build a fresh DataSource per test or per context rather than reconfiguring a shared one","Do all configuration before new HikariDataSource(config) / before the container/before Spring context finishes binding"],"exampleFix":"// before\nHikariDataSource ds = ...; // already started\nds.setJdbcUrl(newUrl); // IllegalStateException: sealed\n\n// after: build a new pool\nHikariConfig cfg = new HikariConfig();\nexistingConfig.copyStateTo(cfg);\ncfg.setJdbcUrl(newUrl);\nHikariDataSource newDs = new HikariDataSource(cfg);\n// runtime-legal resize of the original pool:\n((HikariConfigMXBean) ds).setMaximumPoolSize(20);","handlingStrategy":"validation","validationCode":"if (dataSource.isRunning()) { // pool already started\n   throw new IllegalStateException(\"HikariConfig is sealed after start; create a new HikariConfig/HikariDataSource or use HikariConfigMXBean\");\n}","typeGuard":"static boolean isMutable(HikariDataSource ds) { return !ds.isRunning(); }\n// or for runtime tunables only:\nstatic boolean isRuntimeTunable(String property) {\n   return java.util.Set.of(\"maximumPoolSize\",\"minimumIdle\",\"idleTimeout\",\"maxLifetime\",\"connectionTimeout\").contains(property);\n}","tryCatchPattern":"catch (IllegalStateException e) {\n   if (e.getMessage().contains(\"sealed\")) {\n      // rebuild pool from a copied config instead of mutating\n      HikariConfig fresh = new HikariConfig();\n      oldConfig.copyStateTo(fresh);\n      fresh.setJdbcUrl(newUrl);\n      var newDs = new HikariDataSource(fresh);\n      // swap references, then oldDs.close()\n   } else throw e;\n}","preventionTips":["Treat HikariConfig as build-once: do all configuration before constructing the pool","For runtime changes use HikariConfigMXBean (setMaximumPoolSize etc.) or replace the pool","In tests, build a fresh DataSource per test instead of reconfiguring a shared one"],"tags":["hikaricp","lifecycle","immutability","mxbean","configuration"],"backgroundTag":null,"analyzedSha":"a4d93f4f85517f90e632b795486d7102e933d7ff","analyzedAt":"2026-08-14T12:11:37.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}