{"record":{"id":"1a7ac0d55bdc7845","repo":"hibernate/hibernate-orm","slug":"struct-not-properly-formed","errorCode":null,"errorMessage":"Struct not properly formed: {}","messagePattern":"Struct not properly formed: (.+?)","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/dialect/type/AbstractPostgreSQLStructJdbcType.java","lineNumber":265,"sourceCode":"\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase ')':\n\t\t\t\t\tif ( !inQuote ) {\n\t\t\t\t\t\tif ( column < element ) {\n\t\t\t\t\t\t\tif ( start == i ) {\n\t\t\t\t\t\t\t\tvalues.add( null );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse {\n\t\t\t\t\t\t\t\tvalues.add( string.substring( start, i ) );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn i + 1;\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tthrow new IllegalArgumentException( \"Struct not properly formed: \" + string.subSequence( start, end ) );\n\t}\n\n\tprivate int deserializeStruct(\n\t\t\tString string,\n\t\t\tint begin,\n\t\t\tint quotes,\n\t\t\tObject[] values,\n\t\t\tboolean returnEmbeddable,\n\t\t\tWrapperOptions options) throws SQLException {\n\t\tint column = 0;\n\t\tboolean inQuote = false;\n\t\tStringBuilder escapingSb = null;\n\t\tassert string.charAt( begin ) == '(';\n\t\tint start = begin + 1;\n\t\tfor ( int i = start; i < string.length(); i++ ) {\n\t\t\tfinal char c = string.charAt( i );\n\t\t\tswitch ( c ) {\n\t\t\t\tcase '\\\\':","sourceCodeStart":247,"sourceCodeEnd":283,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/dialect/type/AbstractPostgreSQLStructJdbcType.java#L247-L283","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"-- before: hand-written literal breaks the parser (unbalanced quote/parens)\nINSERT INTO person (info) VALUES ('(1,J\"o''e,)' );\n\n-- after: well-formed composite literal, quotes escaped\nINSERT INTO person (info) VALUES ('(1,\"J\"\"o''e\")' );","handlingStrategy":"try-catch","validationCode":"// Cheap structural pre-check before handing text to the struct parser\nstatic boolean looksLikeStructLiteral(String s) {\n    if (s == null || s.length() < 2 || s.charAt(0) != '(' || s.charAt(s.length() - 1) != ')') return false;\n    int depth = 0; boolean inQuote = false;\n    for (int i = 0; i < s.length(); i++) {\n        char c = s.charAt(i);\n        if (inQuote) { if (c == '\"' && (i + 1 >= s.length() || s.charAt(i + 1) != '\"')) inQuote = false; else if (c == '\"') i++; }\n        else if (c == '\"') inQuote = true;\n        else if (c == '(') depth++;\n        else if (c == ')') { depth--; if (depth == 0 && i != s.length() - 1) return false; }\n    }\n    return depth == 0 && !inQuote;\n}","typeGuard":null,"tryCatchPattern":"try {\n    MyEmbeddable struct = resultSet.getObject(1, MyEmbeddable.class);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().startsWith(\"Struct not properly formed\")) {\n        // e.getMessage() contains the offending substring: log it with the row PK and quarantine the row\n        log.error(\"Malformed struct value: {}\", e.getMessage());\n        return Optional.empty();\n    }\n    throw e;\n}","preventionTips":["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"],"tags":["hibernate","postgresql","struct","embeddable","jdbc","data-parsing"],"backgroundTag":"struct-deserialization-malformed","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}