Skip to content
PostgreSQL

PostgreSQL Error 23502

null value in column violates not-null constraint

Applies to: PostgreSQL 13 to 18 (examples run on PostgreSQL 18.6 in PGlite)
Last technically reviewed 24 October 2026 on PostgreSQL 18.6 (PGlite 0.5.11)
The full message
ERROR:  null value in column "email" of relation "customers" violates not-null constraint
DETAIL:  Failing row contains (1, null, Sara).
Other forms of this message
Making an existing column NOT NULL, or adding a primary key, over rows that hold NULL (same SQLSTATE)
ERROR:  column "code" of relation "products" contains null values
A domain declared NOT NULL
ERROR:  domain email_text does not allow null values
Raised by ON DELETE SET NULL on a NOT NULL column
ERROR:  null value in column "customer_id" of relation "orders" violates not-null constraint
DETAIL:  Failing row contains (10, null).
CONTEXT:  SQL statement "UPDATE ONLY "public"."orders" SET "customer_id" = NULL WHERE $1 OPERATOR(pg_catalog.=) "customer_id""
SQLSTATE
23502
Condition name
not_null_violation
Product
PostgreSQL
Versions
PostgreSQL 13 to 18 (examples run on PostgreSQL 18.6 in PGlite)
Last reviewed
24 October 2026

What it means

SQLSTATE 23502 means a row would store NULL in a column declared NOT NULL (including every primary key column), so PostgreSQL rejected the statement.

A NOT NULL constraint says the column must always have a value. When an INSERT, UPDATE, COPY or a cascading foreign key action would leave it NULL, PostgreSQL stops the statement and raises 23502 (not_null_violation). The message names the column and the table, and DETAIL prints the whole failing row in column order, with null where the value is missing, so you can see exactly what was sent. Primary key columns are NOT NULL automatically.

Two details catch people out. A column default is used only when the column is left out of the INSERT or given the keyword DEFAULT; an explicit NULL, which many ORMs send for unset fields, is stored as NULL and fails. And the check runs on the row PostgreSQL proposes to insert, so INSERT ... ON CONFLICT DO UPDATE fails here when the VALUES list omits a NOT NULL column, even if the row already exists. In PostgreSQL 18 NOT NULL constraints are also stored in pg_constraint with names such as customers_email_not_null, but the error message still names the column, not the constraint.

Common causes

  • The INSERT leaves out a NOT NULL column that has no default.
  • The application or ORM sends an explicit NULL for a field it did not set, which bypasses the column default.
  • An UPDATE sets the column to NULL, directly or through an expression or subquery that returns NULL (for example a lookup that finds no row).
  • An INSERT ... SELECT or COPY brings in source rows with empty values, or a LEFT JOIN in the SELECT produces NULLs.
  • ALTER TABLE ... SET NOT NULL, ADD COLUMN ... NOT NULL without a default, or ADD PRIMARY KEY runs on a table where some rows are NULL ("contains null values").
  • A foreign key with ON DELETE SET NULL points from a column that is also NOT NULL, so deleting the parent fails.

Diagnose it

Read-only These queries only read the system catalogs (pg_catalog, information_schema) and statistics views such as pg_stat_activity and pg_locks. They do not change data, roles or settings, so they are safe to run first.

Which columns are NOT NULL, and which have a default?

SELECT column_name, is_nullable, column_default, is_identity, is_generated
FROM information_schema.columns
WHERE table_schema = 'public'
  AND table_name = 'customers'
ORDER BY ordinal_position;
What to look for

Every column with is_nullable = NO must receive a value. Those with a column_default, an identity (is_identity = YES) or a generated expression fill themselves only when the INSERT omits them; the rest must always be supplied. The order also tells you which value in the DETAIL row belongs to which column.

List the NOT NULL constraints by name (PostgreSQL 18)

From PostgreSQL 18, NOT NULL constraints are rows in pg_constraint with contype n; this query returns nothing on older versions, where the information is only in pg_attribute.attnotnull.

SELECT conname, pg_get_constraintdef(oid) AS definition, convalidated
FROM pg_constraint
WHERE conrelid = 'customers'::regclass
  AND contype = 'n'
ORDER BY conname;
What to look for

Rows such as customers_email_not_null | NOT NULL email | true. convalidated = false means the constraint was added NOT VALID: new rows are checked but old rows may still hold NULL.

Count the NULL rows before adding NOT NULL

SELECT count(*) AS all_rows,
       count(*) FILTER (WHERE code IS NULL) AS null_rows
FROM products;
What to look for

SET NOT NULL, ADD PRIMARY KEY and VALIDATE CONSTRAINT fail with "contains null values" while null_rows is above zero.

How to fix it

Each fix is labelled with what it changes. Try read-only checks and the least invasive fix first, and test changes outside production.

Supply the value, or let the default fill it

Changes data

Provide a real value for every NOT NULL column. If the column has a default, leave it out of the column list or write DEFAULT; do not send NULL. In ORMs, this usually means not mapping the column on insert, or marking it as database-generated.

INSERT INTO customers (email, name) VALUES ('sara@example.com', 'Sara');

-- or, for a column with a default
INSERT INTO events (id, created_at) VALUES (1, DEFAULT);

Give the column a default

Changes schema

When there is a sensible value for rows that do not provide one, add a default. It applies to future inserts that omit the column; it does not change existing rows and does not help when NULL is sent explicitly.

ALTER TABLE events ALTER COLUMN created_at SET DEFAULT now();

Backfill existing NULLs, then add NOT NULL

Changes schema

For "contains null values", fill or remove the NULL rows first, then add the constraint. To add a new required column to a populated table, give it a default in the same statement so existing rows get a value. On PostgreSQL 18 you can also add a named NOT NULL constraint as NOT VALID (only new rows are checked) and validate it after the backfill.

Caution: Choose the backfill value deliberately: a placeholder such as UNKNOWN becomes real data that reports and code will see. Check the count from the diagnostic before and after.

UPDATE products SET code = 'UNKNOWN' WHERE code IS NULL;
ALTER TABLE products ALTER COLUMN code SET NOT NULL;

-- a new required column on a table that already has rows
ALTER TABLE customers ADD COLUMN status text NOT NULL DEFAULT 'active';

Upserts: include every NOT NULL column in the VALUES list

Changes data

ON CONFLICT DO UPDATE checks the proposed row before it finds the conflict, so the VALUES list must be a complete, valid row even when you only mean to update one column. If you really only want to change existing rows, use a plain UPDATE instead.

INSERT INTO accounts (id, owner, note)
VALUES (1, 'Sara', 'updated')
ON CONFLICT (id) DO UPDATE SET note = EXCLUDED.note;

Examples

Leaving out a required column

CREATE TABLE customers (
    id    int GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    email text NOT NULL,
    name  text
);

INSERT INTO customers (name) VALUES ('Sara');
Result
ERROR:  null value in column "email" of relation "customers" violates not-null constraint
DETAIL:  Failing row contains (1, null, Sara).

Reproduced on PGlite 0.5.11 (PostgreSQL 18.6 compiled to WebAssembly), 24 October 2026.

An explicit NULL does not use the column default

CREATE TABLE events (
    id         int,
    created_at timestamptz NOT NULL DEFAULT now()
);

INSERT INTO events (id, created_at) VALUES (1, DEFAULT);  -- works
INSERT INTO events (id, created_at) VALUES (2, NULL);     -- fails
Result
ERROR:  null value in column "created_at" of relation "events" violates not-null constraint
DETAIL:  Failing row contains (2, null).

Reproduced on PGlite 0.5.11 (PostgreSQL 18.6 compiled to WebAssembly), 24 October 2026.

An upsert that omits a NOT NULL column

CREATE TABLE accounts (
    id    int PRIMARY KEY,
    owner text NOT NULL,
    note  text
);
INSERT INTO accounts VALUES (1, 'Sara', 'first');

-- only meant to update note, but the proposed row has owner = NULL
INSERT INTO accounts (id, note) VALUES (1, 'updated')
ON CONFLICT (id) DO UPDATE SET note = EXCLUDED.note;
Result
ERROR:  null value in column "owner" of relation "accounts" violates not-null constraint
DETAIL:  Failing row contains (1, null, updated).

Reproduced on PGlite 0.5.11 (PostgreSQL 18.6 compiled to WebAssembly), 24 October 2026.

Setting NOT NULL on a column that already has NULLs

This form has no DETAIL line, so it does not say which rows are NULL; use the counting query above. ADD PRIMARY KEY on the column and ADD COLUMN ... NOT NULL without a default on a table with rows give the same message.

CREATE TABLE products (id int, code text);
INSERT INTO products VALUES (1, 'A-1'), (2, NULL);

ALTER TABLE products ALTER COLUMN code SET NOT NULL;
Result
ERROR:  column "code" of relation "products" contains null values

Reproduced on PGlite 0.5.11 (PostgreSQL 18.6 compiled to WebAssembly), 24 October 2026.

How to prevent it

Declare NOT NULL on every column that must have a value, with a default where one makes sense, and keep the application from sending NULL for fields it does not set. Validate required fields before the INSERT so users get a clear message, and still handle SQLSTATE 23502 in code, since the database is the final check.

Before tightening a column in a migration, count the NULLs, backfill them and only then add NOT NULL. Do not combine ON DELETE SET NULL with a NOT NULL column, and test imports with a few rows first so empty source values show up early.

Common questions

Why do I get a not-null error when the column has a default?

Because the INSERT sent NULL explicitly. The default is used only when the column is omitted from the INSERT or given the keyword DEFAULT. Remove the column from the statement (or your ORM mapping) or send DEFAULT.

How do I read "Failing row contains"?

It is the complete row PostgreSQL tried to store, in the table's column order, with null for each missing value. Compare it with the column list from information_schema.columns to see which values are missing.

Why does ON CONFLICT DO UPDATE fail with a not-null error for a row that exists?

PostgreSQL checks the proposed row, built from your VALUES list and defaults, before it detects the conflict. Every NOT NULL column without a default must be in the VALUES list, even if only one column is updated.

Is an empty string the same as NULL in PostgreSQL?

No. An empty string '' is a value and passes NOT NULL; only NULL fails. If empty strings should also be rejected, add a CHECK constraint such as CHECK (email <> '').

Learn more on CodeWithSQL

Official documentation

Last technically reviewed 24 October 2026; examples and diagnostics run on PostgreSQL 18.6 (PGlite 0.5.11). SQL-level examples were run in PGlite (PostgreSQL compiled to WebAssembly); examples that need a running server are shown as documented in the PostgreSQL documentation.

Still stuck on a PostgreSQL error?

Browse the full PostgreSQL error library, or get hands-on help from an experienced SQL professional.