hibernate/hibernate-orm · error · IllegalArgumentException

unmatched placeholder start [${property}]

Error message

unmatched placeholder start [${property}]

What it means

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.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/internal/util/config/ConfigurationHelper.java:381

	public static String resolvePlaceHolder(String property) {
		if ( !property.contains( PLACEHOLDER_START ) ) {
			return property;
		}
		final var result = new StringBuilder();
		final char[] chars = property.toCharArray();
		for ( int pos = 0; pos < chars.length; pos++ ) {
			if ( chars[pos] == '$' ) {
				// peek ahead
				if ( chars[pos+1] == '{' ) {
					// we have a placeholder, spin forward till we find the end
					final var systemPropertyName = new StringBuilder();
					int x = pos + 2;
					for ( ; x < chars.length && chars[x] != '}'; x++ ) {
						systemPropertyName.append( chars[x] );
						// if we reach the end of the string w/o finding the
						// matching end, that is an exception
						if ( x == chars.length - 1 ) {
							throw new IllegalArgumentException( "unmatched placeholder start [" + property + "]" );
						}
					}
					final String systemProperty = extractFromSystem( systemPropertyName.toString() );
					result.append( systemProperty == null ? "" : systemProperty );
					pos = x + 1;
					// make sure spinning forward did not put us past the end of the buffer...
					if ( pos >= chars.length ) {
						break;
					}
				}
			}
			result.append( chars[pos] );
		}
		return result.isEmpty() ? null : result.toString();
	}

	private static String extractFromSystem(String systemPropertyName) {
		try {

View on GitHub (pinned to fad1729dce)

Solutions

  1. Add the missing '}' so every '${' has a matching close: jdbc:postgresql://${DB_HOST}/mydb
  2. Verify the property resolves from System properties (System.getProperty) - that is the only source consulted
  3. If the '${' is literal data (password, SQL), replace or escape it so it is not treated as a placeholder

Example fix

# before
hibernate.connection.url=jdbc:postgresql://${DB_HOST

# after
hibernate.connection.url=jdbc:postgresql://${DB_HOST}/mydb
Defensive patterns

Strategy: validation

Validate before calling

static boolean hasBalancedPlaceholders(String value) {
    int open = value.indexOf("${");
    while (open >= 0) {
        if (value.indexOf('}', open + 2) < 0) return false;
        open = value.indexOf("${", open + 2);
    }
    return true;
}

// gate config load
properties.forEach((k, v) -> {
    if (!hasBalancedPlaceholders(v)) throw new IllegalStateException("Unterminated '${' in " + k + "=" + v);
});

Try / catch

try {
    sessionFactory = new Configuration().mergeProperties(props).buildSessionFactory();
} catch (IllegalArgumentException e) {
    if (e.getMessage().contains("unmatched placeholder start")) {
        throw new IllegalStateException("Fix the unterminated ${...} in hibernate properties: " + e.getMessage(), e);
    }
    throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22). Data as JSON: /api/errors/8881631e10d354bc. Report an issue: GitHub.