Skip to content

WebAuthnCredential table not created: VARBINARY(65535) exceeds MariaDB row size limit #286

@devondragon

Description

@devondragon

Summary

The user_credentials table is never created by Hibernate's ddl-auto: update on MariaDB because the generated CREATE TABLE statement exceeds InnoDB's 65,535-byte row size limit. Hibernate silently swallows the error, so the app starts but /user/auth-methods and /user/webauthn/credentials return 500.

Root Cause

WebAuthnCredential.java has two byte[] fields with @Column(length = 65535):

  • attestationObject
  • attestationClientDataJson

With Hibernate 7.x and MariaDB dialect, @Column(length = 65535) on byte[] maps to VARBINARY(65535)not BLOB. Two VARBINARY(65535) columns alone account for 131,074 bytes, which exceeds MariaDB's InnoDB row size limit of 65,535 bytes.

MariaDB rejects the CREATE TABLE with:

ERROR 1118 (42000): Row size too large (> 65535)

Hibernate's ddl-auto: update catches this exception silently and moves on.

Why user_entities Works But user_credentials Doesn't

WebAuthnUserEntity has only small String columns and an FK, so its row size is well under the limit.

Historical Context

Commit cd4b0a1 ("fix: Use length 65535 for WebAuthn blob columns to avoid MySQL MEDIUMBLOB promotion") changed length from 65536 to 65535. Ironically, at length = 65536 Hibernate generates MEDIUMBLOB (stored off-page, no row size issue). At length = 65535 it generates VARBINARY(65535) (stored inline, breaks the row size limit).

Suggested Fix

Add @Lob to the byte[] fields in WebAuthnCredential.java:

import jakarta.persistence.Lob;

// On attestationObject field:
@Lob
@Column(name = "attestation_object")
private byte[] attestationObject;

// On attestationClientDataJson field:
@Lob
@Column(name = "attestation_client_data_json")
private byte[] attestationClientDataJson;

With @Lob, Hibernate maps byte[] to BLOB/LONGBLOB regardless of the length setting. BLOB data is stored off-page in InnoDB, avoiding the row size limit. The length attribute can be removed since @Lob takes precedence.

Optionally add @Lob to publicKey as well (currently 2048 bytes).

Impact

  • Existing deployments where table was never created: Table will be created on next startup with ddl-auto: update
  • Existing deployments where table exists: ddl-auto: update should detect columns are already BLOB and not alter them

Reproduction

  1. Use MariaDB with spring.jpa.hibernate.ddl-auto: update
  2. Start the app — user_entities table is created but user_credentials is not
  3. Hit /user/auth-methods or /user/webauthn/credentials while authenticated → 500 with Table 'springuser.user_credentials' doesn't exist

Discovered while investigating Playwright test failures in the demo app (Issue #67).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions