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
- Copy the substring from the exception message and inspect it -- it marks exactly where the parser lost the structure
- Fix the producer so the literal is a well-formed composite: one parenthesized, comma-separated attribute list with embedded quotes doubled/escaped
- Select the composite as an actual composite/row value (PGobject / struct) instead of its ::text cast so the parser is never used on it
- 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
- Bind @Struct values as real composite/PGobject parameters; never build '(a,b)' text by string concatenation
- When string members may contain , ( ) " \\ , always wrap them in double quotes and escape embedded quotes
- Keep a validation job that checks struct columns for balanced literals after external data loads
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
- Unsupported foreign key part:
- JDBC driver does not support named parameters for setArray.
- PostgreSQL only supports REF_CURSOR parameters as the first
- PostgreSQL only supports accessing REF_CURSOR parameters by
- Class '<componentClassName>' is an '@Embeddable' type and ma
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/1a7ac0d55bdc7845.
Report an issue: GitHub.