{"record":{"id":"8881631e10d354bc","repo":"hibernate/hibernate-orm","slug":"unmatched-placeholder-start-property","errorCode":null,"errorMessage":"unmatched placeholder start [${property}]","messagePattern":"unmatched placeholder start \\[(.+?)\\]","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/internal/util/config/ConfigurationHelper.java","lineNumber":381,"sourceCode":"\tpublic static String resolvePlaceHolder(String property) {\n\t\tif ( !property.contains( PLACEHOLDER_START ) ) {\n\t\t\treturn property;\n\t\t}\n\t\tfinal var result = new StringBuilder();\n\t\tfinal char[] chars = property.toCharArray();\n\t\tfor ( int pos = 0; pos < chars.length; pos++ ) {\n\t\t\tif ( chars[pos] == '$' ) {\n\t\t\t\t// peek ahead\n\t\t\t\tif ( chars[pos+1] == '{' ) {\n\t\t\t\t\t// we have a placeholder, spin forward till we find the end\n\t\t\t\t\tfinal var systemPropertyName = new StringBuilder();\n\t\t\t\t\tint x = pos + 2;\n\t\t\t\t\tfor ( ; x < chars.length && chars[x] != '}'; x++ ) {\n\t\t\t\t\t\tsystemPropertyName.append( chars[x] );\n\t\t\t\t\t\t// if we reach the end of the string w/o finding the\n\t\t\t\t\t\t// matching end, that is an exception\n\t\t\t\t\t\tif ( x == chars.length - 1 ) {\n\t\t\t\t\t\t\tthrow new IllegalArgumentException( \"unmatched placeholder start [\" + property + \"]\" );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tfinal String systemProperty = extractFromSystem( systemPropertyName.toString() );\n\t\t\t\t\tresult.append( systemProperty == null ? \"\" : systemProperty );\n\t\t\t\t\tpos = x + 1;\n\t\t\t\t\t// make sure spinning forward did not put us past the end of the buffer...\n\t\t\t\t\tif ( pos >= chars.length ) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tresult.append( chars[pos] );\n\t\t}\n\t\treturn result.isEmpty() ? null : result.toString();\n\t}\n\n\tprivate static String extractFromSystem(String systemPropertyName) {\n\t\ttry {","sourceCodeStart":363,"sourceCodeEnd":399,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/internal/util/config/ConfigurationHelper.java#L363-L399","documentation":"ConfigurationHelper.resolvePlaceHolder interpolates ${...} markers inside property values against System.getProperty during Hibernate configuration resolution. When a value opens a placeholder with '${' but the scan reaches the end of the string without finding the matching '}', this IllegalArgumentException is thrown, echoing the whole malformed property value. Unresolvable-but-well-formed placeholders are NOT an error (they are replaced with an empty string); only the unterminated form throws.","triggerScenarios":"Any hibernate.* property whose value contains an unterminated placeholder, e.g. hibernate.connection.url=jdbc:postgresql://${DB_HOST (missing '}'); or a literal '${' sequence inside a password, SQL fragment, or template residue like ${artifactId} left in a resource-filtered config file.","commonSituations":"Typos that delete the closing brace; copy-pasting Spring-style ${VAR} placeholders (note: Hibernate resolves them from JVM system properties only, and does not support default-value syntax); Maven/Gradle resource filtering leaving unclosed placeholders in hibernate.properties/persistence.xml.","solutions":["Add the missing '}' so every '${' has a matching close: jdbc:postgresql://${DB_HOST}/mydb","Verify the property resolves from System properties (System.getProperty) - that is the only source consulted","If the '${' is literal data (password, SQL), replace or escape it so it is not treated as a placeholder"],"exampleFix":"# before\nhibernate.connection.url=jdbc:postgresql://${DB_HOST\n\n# after\nhibernate.connection.url=jdbc:postgresql://${DB_HOST}/mydb","handlingStrategy":"validation","validationCode":"static boolean hasBalancedPlaceholders(String value) {\n    int open = value.indexOf(\"${\");\n    while (open >= 0) {\n        if (value.indexOf('}', open + 2) < 0) return false;\n        open = value.indexOf(\"${\", open + 2);\n    }\n    return true;\n}\n\n// gate config load\nproperties.forEach((k, v) -> {\n    if (!hasBalancedPlaceholders(v)) throw new IllegalStateException(\"Unterminated '${' in \" + k + \"=\" + v);\n});","typeGuard":null,"tryCatchPattern":"try {\n    sessionFactory = new Configuration().mergeProperties(props).buildSessionFactory();\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().contains(\"unmatched placeholder start\")) {\n        throw new IllegalStateException(\"Fix the unterminated ${...} in hibernate properties: \" + e.getMessage(), e);\n    }\n    throw e;\n}","preventionTips":["Lint config files for '${' without a matching '}' before deploy","Remember Hibernate resolves placeholders from JVM system properties only - set them with -D or System.setProperty","Escape or avoid literal '${' sequences in passwords/SQL values"],"tags":["configuration","placeholder","interpolation"],"backgroundTag":"unresolved-placeholder","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}