Short answer: The JSON specification has no size limit. Every real limit comes from somewhere else: the memory available to the parser, V8's string cap of roughly 512 MB, and the body-size settings of whatever is in front of your code (Express defaults to 100kb, Nginx to 1 MB, AWS API Gateway to 10 MB). As a rule of thumb, single-digit megabytes work everywhere, tens of megabytes need the right settings, and hundreds of megabytes need streaming, pagination, or JSON Lines instead of one giant document.
The nightly export worked for a year. Then the customer table crossed some threshold, the file hit a few hundred megabytes, and the import script started dying with an out-of-memory error at 3 a.m. Nothing in the JSON changed. What changed is that the file crossed a limit nobody knew was there. This post lists those limits, the exact error each one produces, and what to do when you hit them.
Explore a manageable sample in the JSON Viewer. For large inputs, check the file limit and your browser memory before loading the full payload.
The Spec Has No Limit, Which Is the Problem
RFC 8259 section 9 says it plainly: an implementation may set limits on the size of texts it accepts, on nesting depth, on the range of numbers, and on string length. The standard itself sets none. That is deliberate; JSON is a grammar, not a transport. But it means "is this file too big?" has no universal answer. The answer is always "too big for what?"
There are three distinct kinds of limit, and they fail differently:
- Parser and runtime limits: string length caps and heap size. These crash the process.
- Transport limits: HTTP body-size settings in frameworks, proxies, and cloud gateways. These return 413.
- Storage limits: maximum document size in a database or browser storage. These reject the write.
Where the Limits Actually Live
The figures below are defaults as documented by each project at the time of writing. Treat them as approximate and check the current docs before relying on one.
| Layer | Default limit | What you see |
|---|---|---|
| V8 string length (Chrome, Node) | ~512 MB of characters on 64-bit | Cannot create a string longer than 0x1fffffe8 characters / Invalid string length |
| Node.js default heap | A few GB, version dependent | FATAL ERROR: Reached heap limit / JavaScript heap out of memory |
| Browser localStorage | ~5 MB per origin (approx.) | QuotaExceededError |
| Express express.json() | 100kb | 413 PayloadTooLargeError: request entity too large |
| Nginx client_max_body_size | 1m | 413 Request Entity Too Large |
| AWS API Gateway (REST) | 10 MB payload | 413 Request Entity Too Large |
| AWS Lambda synchronous invoke | 6 MB request and response | RequestEntityTooLargeException |
| MongoDB document | 16 MB (BSON) | BSONObjectTooLarge / document is larger than the maximum size |
| Elasticsearch http.max_content_length | 100mb | 413 / entity content is too long |
| JSON Prettifier upload | 5 MB | Upload rejected in the browser |
JSON Size Limits in the Browser
JSON.parse operates on a string, so the first ceiling is the engine's maximum string length. In V8 (Chrome, Edge, Node) that is 0x1fffffe8 characters, a little under 512 MB on 64-bit builds; older versions were lower. Long before you reach it, two softer limits apply:
- The parsed object graph takes several times more memory than the source text, because every key, string, and number becomes a separate heap allocation. A 200 MB file can easily need over a gigabyte to hold as objects.
JSON.parseis synchronous and blocks the main thread. Parsing even 50 MB freezes the UI for a noticeable moment. Move large parses to a Web Worker.
Browser storage has its own numbers: localStorage is around 5 MB per origin in most browsers, so caching a large API response there throws QuotaExceededError. IndexedDB allows far more but stores structured data, not strings, so the memory point above still applies.
Online formatters have the same constraint plus rendering cost: syntax-highlighting a hundred-megabyte document is slower than parsing it. JSON Prettifier caps uploads at 5 MB for this reason, which covers nearly every API response and config file. For bigger files, the validating large JSON in the browser post explains what works and what does not, and best formatters for large responses compares the options.
JSON Size Limits in Node.js
Node shares V8's string cap, and you hit it earlier than you expect because reading a file with an encoding builds the string in one go:
const text = fs.readFileSync("huge.json", "utf8");
// Error: Cannot create a string longer than 0x1fffffe8 characters
// code: 'ERR_STRING_TOO_LONG'
Below that cap, the heap limit takes over. Node's default old-space size is a few gigabytes on 64-bit systems (the exact figure depends on the version and available memory), and a parse that needs more ends with:
FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
You can raise it with node --max-old-space-size=8192 script.js, and sometimes that is the pragmatic fix for a one-off import. It is not a fix for a pipeline that grows. For that, stream.
Streaming with stream-json
stream-json parses the file as it is read and emits each top-level array element as a separate object, so memory stays flat no matter how large the file is:
npm install stream-json stream-chain
const fs = require("fs");
const { chain } = require("stream-chain");
const { parser } = require("stream-json");
const { streamArray } = require("stream-json/streamers/StreamArray");
const pipeline = chain([
fs.createReadStream("orders.json"), // [ {...}, {...}, ... ]
parser(),
streamArray(),
]);
let total = 0;
pipeline.on("data", ({ value }) => {
total += value.amount;
});
pipeline.on("end", () => console.log("total:", total));
The trade-off is that you lose random access. You see each record once, in order, and you have to decide what to keep. For most imports and aggregations that is exactly the shape you want anyway.
JSON Size Limits in Python
Python has no practical string-length cap on 64-bit systems, so json.load is limited only by RAM. The memory multiplier is similar to Node: a parsed dict-and-list tree is several times larger than the text, and Python objects carry more overhead per item than V8 objects. When json.load is killed by the OS or raises MemoryError, switch to ijson, which walks the file incrementally:
pip install ijson
import ijson
total = 0
with open("orders.json", "rb") as f:
for order in ijson.items(f, "item"): # "item" = each element of the top-level array
total += order["amount"]
print(total)
The second argument is a path prefix. "item" means each element of a top-level array; "results.item" would iterate the array under a results key. Open the file in binary mode; ijson handles the UTF-8 decoding itself.
HTTP Body Limits: Why You Get 413
A 413 means a layer in front of your handler refused the body. Work outward from the app, because each layer has its own default and the smallest one wins.
Express
// default is "100kb"; a 2 MB POST fails with:
// PayloadTooLargeError: request entity too large
app.use(express.json({ limit: "10mb" }));
Nginx
# nginx.conf, inside http, server, or location
client_max_body_size 20m; # default is 1m
After editing, nginx -t then reload. If the 413 page is Nginx's own HTML rather than your app's JSON error, this is the layer.
Cloud gateways
Managed gateways generally cannot be raised. AWS API Gateway REST APIs reject payloads over 10 MB, and a synchronous Lambda invocation is limited to 6 MB in each direction, as documented by AWS. The standard workaround is to upload large bodies directly to object storage (a pre-signed S3 URL, for example) and send the API a reference, not the data.
The JSON in REST APIs guide covers the response side, where the same limits apply and pagination is the usual answer.
Strategies for Large JSON
Pick by whether you control the producer, the consumer, or both.
- Paginate. If the data comes from an API, ask for it in pages of a few hundred or thousand records. It is the cheapest fix and the one every consumer can handle.
- Switch to JSON Lines. One JSON object per line, no outer array. Any line reader can process it with constant memory, it appends cleanly, and it splits across files with
split -l. See JSON Lines explained for the format and converters. - Stream the parse. When the file is already a single giant document and you cannot change it, use
stream-json,ijson, Jackson's streaming API, Go'sjson.DecoderwithToken(), orjq --stream. - Compress in transit. Repeated keys can make JSON compress well, but measure your payloads rather than assuming a saving. Negotiate response compression separately from compressed uploads, and enforce decoded-size limits as well as wire-size limits. Use these six JSON API compression checks to test headers, decoder ownership, resource budgets, and streaming delivery before enabling it.
- Minify and trim. Removing whitespace, as covered in how to minify JSON, typically shaves a modest percentage; dropping null fields and shortening repeated keys can shave more. Useful at the margin, not a substitute for the options above.
- Consider a binary format. If both ends are yours and the volume is sustained, Protocol Buffers or MessagePack cut size and parse time substantially. The JSON vs Protobuf comparison lays out when that is worth the loss of readability.
Common Mistakes
- Raising the limit instead of asking why. Bumping
express.jsonto50mbworks until it is 60. Limits exist partly as a denial-of-service guard; a huge limit lets any client make your server allocate hundreds of megabytes per request. - Reading the whole file to count records.
JSON.parse(text).lengthon a multi-gigabyte array is the classic 3 a.m. crash. Stream it, or if it is JSON Lines,wc -l. - Pretty-printing large exports. Indentation can add 20 to 40 percent to file size for deeply nested data and buys nothing for a file no human will read. Write compact; format a sample when you need to inspect it.
- Assuming the 413 came from your app. It often came from the proxy. Check the response body and
Serverheader before touching application config. - Storing arrays-of-everything in one MongoDB document. The 16 MB BSON cap is per document. Growing a single document with an ever-expanding array hits it eventually; model the items as separate documents.