Value Too Long Varchar Truncation
An INSERT or UPDATE that used to succeed now fails with 'ERROR: value too long for type character varying(N)' and SQLSTATE 22001, and the failure is...
In 10 seconds
Writes to a table have started failing with 'ERROR: value too long for type character varying(N)'. A column was declared varchar(N) with a length someone guessed when the schema was first written, and the application has now produced a value longer than N. PostgreSQL does not truncate the value and does not warn -- it rejects the entire INSERT or UPDATE with SQLSTATE 22001. The immediate fix is trivial: widen the column, which on a modern server is a metadata-only change with no table rewrite. The dangerous part is everything you cannot see: every other varchar(N) column that is one long value away from the same outage. This recipe reproduces the failure on a varchar(20) username column, shows the free single-column lookup and the premium fleet-wide headroom audit that finds the next outage before it happens, widens the column, and replaces the length guess with an explicit, changeable CHECK constraint.
Why This Happens
A varchar(N) (character varying(N)) column stores its declared maximum length in the system catalog as pg_attribute.atttypmod, encoded as the character limit plus 4 (the varlena header size, VARHDRSZ), so a varchar(20) column has atttypmod = 24 and the true limit is atttypmod -- 4 = 20. When a value is stored, PostgreSQL enforces that limit and raises error 22001 (string_data_right_truncation) if the value is longer -- it never truncates, because silently losing data would be worse than failing. Crucially, varchar(N), varchar, and text are the same underlying storage with the same performance; the only difference is that varchar(N) carries this length check. That means the N is almost always a guess about future data, and when the guess is wrong the column does not degrade gracefully -- it takes writes down. Widening the limit (increasing N, or converting to varchar/text) only changes the catalog's atttypmod and is therefore a metadata-only operation with no table rewrite; shrinking the limit, by contrast, must scan the table to validate every existing row and will itself raise 22001 if any value is too long.
Diagnose
Free Tier -- Basic Approach
The free diagnosis is reading the error and confirming the column. The 22001 message already names the type and the limit it violated; one catalog lookup confirms which column is declared character varying(20). That is enough to fix the column you were just bitten by.
SELECT a.attname AS column_name,
format_type(a.atttypid, a.atttypmod) AS declared_type,
a.atttypmod - 4 AS max_chars
FROM pg_attribute a
WHERE a.attrelid = 'users'::regclass AND a.attname = 'username';
column_name | declared_type | max_chars -------------+-----------------------+----------- username | character varying(20) | 20 (1 row)
Look up the offending column in pg_attribute and render its type with format_type: username is 'character varying(20)' with max_chars = atttypmod -- 4 = 20 -- exactly the cap the error named. The decision it drives is immediate: this one column needs widening.
What it does not tell you is whether any other column is about to fail the same way, which is what the premium audit answers.
Fix
The fix is to widen the column to a length that fits the real data. Increasing a varchar limit (or dropping it) is a catalog-only change in modern PostgreSQL -- no table rewrite, no row scan -- so it is fast and safe even on a large table.
ALTER TABLE users ALTER COLUMN username TYPE varchar(64);
ALTER TABLE
INSERT INTO users(username, status, bio) VALUES (repeat('y',25), 'active', 'now fits');
INSERT 0 1
SELECT format_type(atttypid, atttypmod) AS new_type FROM pg_attribute WHERE attrelid = 'users'::regclass AND attname = 'username';
new_type ----------------------- character varying(64) (1 row)
ALTER TABLE users ALTER COLUMN username TYPE varchar(64) updates only the column's atttypmod; the 'ALTER TABLE' returns immediately with no rewrite. The identical 25-character insert that raised 22001 now returns 'INSERT 0 1', and the column reports its wider declared type, 'character varying(64)'.
Widen to a size that comfortably covers your real maximum, or convert to text and enforce the true limit with a CHECK constraint (see prevention).
Verify
Healthy-state proof: a value that used to fail now inserts, the column reports its widened type, and the headroom that was 0 is now comfortably positive -- the landmine is cleared, not just the single row.
INSERT INTO users(username, status, bio) VALUES (repeat('z',30), 'active', 'verify insert');
INSERT 0 1
SELECT format_type(atttypid, atttypmod) AS declared_type FROM pg_attribute WHERE attrelid = 'users'::regclass AND attname = 'username';
declared_type ----------------------- character varying(64) (1 row)
SELECT (atttypmod - 4) AS declared_limit,
(SELECT max(length(username)) FROM users) AS max_observed,
(atttypmod - 4) - (SELECT max(length(username)) FROM users) AS headroom
FROM pg_attribute WHERE attrelid = 'users'::regclass AND attname = 'username';
declared_limit | max_observed | headroom
----------------+--------------+----------
64 | 30 | 34
(1 row)
Insert a 30-character username (well past the old 20-character limit): it returns 'INSERT 0 1'. The column now reports 'character varying(64)', and the headroom calculation -- declared limit (64) minus the longest stored value (30) -- is 34.
A positive, generous headroom is the durable signal that this column is no longer at risk.
⏪ Rollback
Every change in this recipe is reversible and non-destructive. Widening with ALTER TABLE ... ALTER COLUMN ... TYPE varchar(64) or TYPE text only updates the catalog; to go back you would ALTER it to a narrower type, which is only safe if no stored value exceeds the new limit (otherwise it raises 22001 during the validating scan). The CHECK constraint added in prevention is removed with ALTER TABLE ... DROP CONSTRAINT username_len_chk. No data is rewritten or lost by any step -- the failing writes simply never landed, and the surviving rows are untouched.
📦 Version Matrix (PG 14--18)
The length check and its fix are core character-type behaviour, identical across supported releases. On PostgreSQL 14 through 18 the over-length insert fails with the same 22001 'value too long' error, and after widening the same insert succeeds.
| Version | Behavior | Version Notes |
|---|---|---|
PG 14.23 |
broken=value too long for type character varying | ✗ Not available -- use stats_reset age |
PG 15.18 |
broken=value too long for type character varying | ✗ Not available -- use stats_reset age |
PG 16.14 |
broken=value too long for type character varying | ✗ Not available -- use stats_reset age |
PG 17.10 |
broken=value too long for type character varying | ✗ Not available -- use stats_reset age |
PG 18.4 |
broken=value too long for type character varying | ✗ Not available -- use stats_reset age |
⚠ Common Mistakes
Stop this incident before it pages you
The free runbook clears today's failure. Pro adds the standing guardrails -- the alert query, the dashboard, and the managed-service knobs -- so it never becomes an incident again.
- ✓ A ready-to-paste alert query with the right thresholds.
- ✓ The topology change that prevents recurrence.
- ✓ The exact knobs on RDS, Azure Flexible Server & Cloud SQL.
Frequently Asked Questions
Why doesn't PostgreSQL just truncate the value to fit?
Because silently discarding data is almost always worse than refusing the write. The SQL standard behaviour for an over-length assignment to character varying is an error (string_data_right_truncation, SQLSTATE 22001), and PostgreSQL follows it. If you genuinely want truncation you must do it explicitly in the application or with left(value, N) in the query -- the database will never do it for you.
Is widening the column going to lock the table or rewrite it?
No. Increasing a varchar length, or converting varchar(N) to varchar or text, changes only the column's atttypmod in the catalog -- it is a metadata-only operation with no table rewrite and only a brief lock. This has been true since PostgreSQL 9.2. Shrinking the limit is the expensive direction, because it must scan every row to prove the new limit holds.
Should I use varchar(N) or text?
Prefer text unless you have a genuine, externally-mandated length limit. Since text and varchar(N) store and perform identically, varchar(N) only adds a brittle failure mode when your length guess is wrong. When you do need a real limit, express it as a CHECK constraint (for example CHECK (char_length(col) <= 100)) -- it is explicit, self-documenting, and can be relaxed later without a table rewrite.
How do I find which columns are about to hit this error?
That is the premium step. Decode pg_attribute.atttypmod (declared limit = atttypmod -- 4) for every varchar/char column, then measure the longest value actually stored in each and compute headroom = limit minus longest stored. A column at or near headroom 0 is one write away from a 22001 outage. Ranking by headroom turns a reactive fix into a proactive audit you can run fleet-wide.
What does the (N) in character varying(N) map to in the catalog?
It is stored in pg_attribute.atttypmod as N + 4: the extra 4 bytes are VARHDRSZ, the varlena length header. So a varchar(20) column has atttypmod = 24, and you recover the character limit with atttypmod -- 4. A plain varchar with no length has atttypmod = -1, which is why the audit filters on atttypmod > 0 to consider only length-constrained columns.