PostgreSQL Error 2203D: Too Many JSON Array Elements
PostgreSQL error code 2203D (too many json array elements) is raised when a JSON array being processed or stored exceeds PostgreSQL's internal limit on the number of elements it can handle in a single array structure. This typically surfaces in data-heavy applications dealing with time-series data, log aggregation, or large API payloads where JSON arrays grow unboundedly over time.
Top 3 Causes
1. Storing Massive JSON Arrays in a Single Column
Inserting a JSON document with millions of elements into a single column hits PostgreSQL's internal memory allocation limits.
-- Problematic: inserting a huge array directly
INSERT INTO sensor_data (device_id, readings)
VALUES (1, '[...millions of elements...]'::jsonb);
-- Check array size before inserting
SELECT jsonb_array_length(
'["elem1","elem2","elem3"]'::jsonb
) AS array_size;
-- Safe insert with size guard
DO $$
DECLARE
v_data JSONB := '[...your data...]'::jsonb;
BEGIN
IF jsonb_array_length(v_data) > 100000 THEN
RAISE EXCEPTION 'Array too large: % elements', jsonb_array_length(v_data)
USING ERRCODE = '2203D';
END IF;
INSERT INTO sensor_data (device_id, readings) VALUES (1, v_data);
END;
$$;
2. Aggregating Too Many Rows with json_agg()
Using json_agg() or jsonb_agg() without a LIMIT on millions of rows creates an oversized array in a single pass.
-- Problematic: aggregating an entire large table
SELECT json_agg(t) FROM orders t; -- Can fail with 2203D
-- Fix: limit the result set before aggregating
SELECT json_agg(sub)
FROM (
SELECT id, customer_id, total_amount, created_at
FROM orders
WHERE created_at >= NOW() - INTERVAL '24 hours'
ORDER BY created_at DESC
LIMIT 5000
) sub;
-- Fix: paginate aggregation in a loop
DO $$
DECLARE
v_offset INT := 0;
v_limit INT := 5000;
v_chunk JSONB;
BEGIN
LOOP
SELECT jsonb_agg(row_to_json(t)::jsonb)
INTO v_chunk
FROM (
SELECT id, customer_id, total_amount
FROM orders
ORDER BY id
LIMIT v_limit OFFSET v_offset
) t;
EXIT WHEN v_chunk IS NULL;
INSERT INTO export_chunks (offset_index, data)
VALUES (v_offset, v_chunk);
v_offset := v_offset + v_limit;
END LOOP;
END;
$$;
3. Unnesting an Already Oversized Stored Array
If a column already contains a huge JSON array, calling jsonb_array_elements() on it at query time triggers the error.
-- Identify offending rows first
SELECT id,
jsonb_array_length(payload) AS array_size
FROM event_logs
WHERE jsonb_typeof(payload) = 'array'
ORDER BY array_size DESC
LIMIT 10;
-- Safe unnest: process only the first N elements
SELECT elem
FROM jsonb_array_elements(
(SELECT payload FROM event_logs WHERE id = 42)
) WITH ORDINALITY AS t(elem, idx)
WHERE idx <= 10000; -- cap at 10,000 elements
-- Reusable safe wrapper function
CREATE OR REPLACE FUNCTION safe_unnest_json(p_data JSONB, p_max INT DEFAULT 10000)
RETURNS SETOF JSONB LANGUAGE plpgsql AS $$
BEGIN
RETURN QUERY
SELECT elem
FROM jsonb_array_elements(p_data) WITH ORDINALITY AS t(elem, idx)
WHERE idx <= p_max;
END;
$$;
Quick Fix Solutions
- Normalize your schema: Avoid storing large arrays in a single column. Use child tables with foreign keys instead.
- Add a CHECK constraint: Prevent oversized arrays from ever being written to disk.
ALTER TABLE event_logs
ADD CONSTRAINT chk_payload_size
CHECK (
jsonb_typeof(payload) != 'array'
OR jsonb_array_length(payload) <= 10000
);
-
Apply LIMIT before aggregating: Always filter or paginate before calling
json_agg()orjsonb_agg(). -
Use streaming or cursors: For bulk exports, use a
CURSORto process data incrementally rather than loading everything into memory at once.
-- Cursor-based safe processing
BEGIN;
DECLARE big_cur CURSOR FOR
SELECT id, payload FROM event_logs WHERE jsonb_typeof(payload) = 'array';
FETCH 1000 FROM big_cur;
-- process batch, then FETCH next 1000...
COMMIT;
Prevention Tips
1. Enforce Array Size Limits at the Database Layer
Add CHECK constraints or BEFORE INSERT/UPDATE triggers to validate JSON array sizes before data lands in the table. This catches bugs in application code and unexpected upstream data issues before they become production incidents.
2. Monitor JSON Column Sizes Regularly
Schedule a monitoring query to alert you when array sizes approach dangerous thresholds. Catching a growing array early gives you time to refactor before hitting the hard limit.
-- Weekly monitoring: find rows with large JSON arrays
SELECT id,
jsonb_array_length(payload) AS arr_size
FROM event_logs
WHERE jsonb_typeof(payload) = 'array'
AND jsonb_array_length(payload) > 5000
ORDER BY arr_size DESC;
Related Errors
| Error Code | Name | Description |
|---|---|---|
22032 |
invalid_json_text |
Malformed JSON string, often precedes array size issues |
2203F |
too many json object members |
Same concept but for JSON object keys instead of array elements |
54000 |
program_limit_exceeded |
General PostgreSQL resource limit exceeded, can co-occur with deep JSON nesting |
📖 Want a more detailed guide?
Check out the full in-depth version (Korean) on oraerror.com — includes detailed analysis, additional SQL examples, and prevention tips.
Top comments (0)