Codebusters

json_decode Returns NULL in PHP? 8 Causes I Tested and How to Fix Each

Thursday 29 October 2026

Your code looks right, the file is there, and json_decode() still gives you null. It does that for at least 8 different reasons, and none of them show up unless you ask. I tested each one on PHP 8.2.35 on my own server, so here is what really causes a null and how to tell them apart in about 30 seconds.

Step 1: ask PHP what went wrong

The null on its own tells you nothing. Straight after the failing call, run json_last_error_msg() and it will give you the reason. Better still, pass JSON_THROW_ON_ERROR and PHP throws an exception with the same message instead of failing quietly.

$data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
// JsonException: Syntax error (4)

That one flag turns a mystery into a line number in your log, and it’s worth adding to every json_decode() call you write. (It only throws on a real error, so a valid null in the JSON is still fine.)

Table of json_decode inputs that return null in PHP 8.2 and the json_last_error_msg result for each
8 inputs that give null, and the message PHP gives for each (PHP 8.2.35)

The syntax mistakes (error 4)

“Syntax error” covers the most common causes, and 6 of my 9 test inputs gave exactly that message. A trailing comma, as in {"a":1,}, fails. So do single quotes, {'a':1}, unquoted keys such as {a:1}, a // comment after the data, and an empty string. JSON is stricter than JavaScript, and PHP won’t forgive any of it.

The invisible one: a BOM

If the file looks perfect and still fails with “Syntax error”, suspect a byte order mark. A BOM is 3 invisible bytes (EF BB BF) at the very start, and some Windows editors add one when you save. I put one in front of {"a":1} and json_decode() returned null with error 4. Strip it before decoding and it works:

$json = preg_replace('/^\xEF\xBB\xBF/', '', $json);

Encoding and control characters (errors 3 and 5)

A raw tab inside a string gives “Control character error”, so tabs and newlines inside values must be written as \t and \n. A Latin-1 character such as é (byte E9) gives “Malformed UTF-8 characters”. If the data really is Latin-1, convert it with mb_convert_encoding($json, 'UTF-8', 'ISO-8859-1') and you get café. If you can live with a replacement character, the JSON_INVALID_UTF8_SUBSTITUTE flag decodes it to caf� instead of failing.

Too deep: 512 levels is the default

I built 600 nested arrays and json_decode() returned null with “Maximum stack depth exceeded”. The default depth is 512. Raising it to 1000 made the same string decode fine. You’ll rarely hit this by accident, but a runaway recursive structure will do it.

The sneaky one: reading a file while it’s being written

This is the cause I’d check first for a script that runs from cron, because it happens only some of the time. I keep small JSON state files for my cron scripts, and a script that reads one while another is part-way through writing it gets half a file. To measure how bad it is, I ran a writer in a loop for 3 seconds, rewriting a 20,000-item JSON file with file_put_contents(), while a reader decoded it as fast as it could. The reader made 4,451 reads and json_decode() returned null on 2,017 of them, which is 45%!

The fix is to write to a temporary file and then rename() it over the real one, because a rename swaps the whole file in one step. Same test, same 3 seconds: 2,357 reads and 0 nulls.

file_put_contents('state.json.tmp', json_encode($data));
rename('state.json.tmp', 'state.json');

If several scripts write the same file, you also need a lock, which I cover in reading and writing JSON files in PHP and stopping a cron job running twice.

A real null is not an error

One last trap. If the JSON is the word null, json_decode() returns null and json_last_error() is 0 (no error). So never write if (!$data) to test for failure. Test json_last_error() !== JSON_ERROR_NONE, or just use the throw flag and a try block.

So that’s 8 causes, 1 flag that finds all of them, and a file I’d happily never again read halfway through a write!