hibernate/hibernate-orm · error · IllegalArgumentException

Struct not properly formed: {}

Error message

Struct not properly formed: {}

What it means

For @Struct-mapped embeddables on PostgreSQL, AbstractPostgreSQLStructJdbcType parses the text form of composite values -- '(v1,v2,...)' -- with a hand-rolled character scanner (bounded by start..end). If the scanner consumes the region without ever hitting the structural terminator (the closing parenthesis at the right nesting level), it throws IllegalArgumentException('Struct not properly formed: <offending substring>') with the exact substring where parsing derailed. This site is the bounded parser (used when deserializing a struct region out of a larger string).

Source

Thrown at hibernate-core/src/main/java/org/hibernate/dialect/type/AbstractPostgreSQLStructJdbcType.java:265

					}
					break;
				case ')':
					if ( !inQuote ) {
						if ( column < element ) {
							if ( start == i ) {
								values.add( null );
							}
							else {
								values.add( string.substring( start, i ) );
							}
						}
						return i + 1;
					}
					break;
			}
		}

		throw new IllegalArgumentException( "Struct not properly formed: " + string.subSequence( start, end ) );
	}

	private int deserializeStruct(
			String string,
			int begin,
			int quotes,
			Object[] values,
			boolean returnEmbeddable,
			WrapperOptions options) throws SQLException {
		int column = 0;
		boolean inQuote = false;
		StringBuilder escapingSb = null;
		assert string.charAt( begin ) == '(';
		int start = begin + 1;
		for ( int i = start; i < string.length(); i++ ) {
			final char c = string.charAt( i );
			switch ( c ) {
				case '\\':

View on GitHub (pinned to fad1729dce)

Solutions

  1. Copy the substring from the exception message and inspect it -- it marks exactly where the parser lost the structure
  2. Fix the producer so the literal is a well-formed composite: one parenthesized, comma-separated attribute list with embedded quotes doubled/escaped
  3. Select the composite as an actual composite/row value (PGobject / struct) instead of its ::text cast so the parser is never used on it
  4. Verify @Struct(name=...) attribute names and order match the PostgreSQL composite type definition

Example fix

-- before: hand-written literal breaks the parser (unbalanced quote/parens)
INSERT INTO person (info) VALUES ('(1,J"o''e,)' );

-- after: well-formed composite literal, quotes escaped
INSERT INTO person (info) VALUES ('(1,"J""o''e")' );
Defensive patterns

Strategy: try-catch

Validate before calling

// Cheap structural pre-check before handing text to the struct parser
static boolean looksLikeStructLiteral(String s) {
    if (s == null || s.length() < 2 || s.charAt(0) != '(' || s.charAt(s.length() - 1) != ')') return false;
    int depth = 0; boolean inQuote = false;
    for (int i = 0; i < s.length(); i++) {
        char c = s.charAt(i);
        if (inQuote) { if (c == '"' && (i + 1 >= s.length() || s.charAt(i + 1) != '"')) inQuote = false; else if (c == '"') i++; }
        else if (c == '"') inQuote = true;
        else if (c == '(') depth++;
        else if (c == ')') { depth--; if (depth == 0 && i != s.length() - 1) return false; }
    }
    return depth == 0 && !inQuote;
}

Try / catch

try {
    MyEmbeddable struct = resultSet.getObject(1, MyEmbeddable.class);
} catch (IllegalArgumentException e) {
    if (e.getMessage().startsWith("Struct not properly formed")) {
        // e.getMessage() contains the offending substring: log it with the row PK and quarantine the row
        log.error("Malformed struct value: {}", e.getMessage());
        return Optional.empty();
    }
    throw e;
}

Prevention

When it happens

Trigger: Reading a value into an @Struct embeddable whose text representation is not a valid composite literal: unbalanced parentheses, a missing comma between attributes, unescaped quotes/backslashes inside string members, or a non-struct string (e.g. '1,foo' without enclosing parens) being handed to the struct JDBC type -- typically from native queries, SQL functions returning row()/text casts, or raw-SQL inserts with hand-built literals.

Common situations: Native queries or @Formula selections returning row_to_string/text casts into struct-mapped embeddables; hand-written INSERTs populating struct columns without proper escaping; @Struct attribute name/order mismatch with the DB composite type shifting how the scanner walks delimiters; data imported by external tools mangling quote escaping.

Related errors


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