Home DevOps & Cloud Security Software Engineering AI & Machine Learning Web Development Developer Tools Programming Languages Databases Architecture & Systems Design Emerging Tech About
Programming Languages

json.decoder.JSONDecodeError: Expecting value: line 1 column 1

NanoTech Insight
NanoTech Insight
2026-10-13
How this article was made: drafted with AI assistance and grounded in the sources linked at the end of the article. Spotted an error? Let us know.
Python code in a terminal showing JSON parsing

This error means Python's JSON parser received something it cannot start parsing: an empty string, a string of only whitespace, HTML from an error page, or a file with a UTF-8 BOM (byte order mark). The fix is nearly always the same β€” check what you're actually handing to json.loads() or json.load() before you call it.

What the error looks like

Traceback (most recent call last):
  File "fetch.py", line 8, in <module>
    data = json.loads(response_text)
  File "/usr/lib/python3.12/json/__init__.py", line 346, in loads
    return _default_decoder.decode(s)
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

JSONDecodeError is a subclass of ValueError, so you can catch it with either. The message "Expecting value: line 1 column 1 (char 0)" pinpoints column 1, meaning the parser gave up at the very first character β€” it never even got started.

The four common causes

1. Empty string or empty file

This is the most frequent cause. An HTTP request returned an empty body, a file was created but never written to, or a variable was initialized to "" and never populated.

$ python3
>>> import json
>>> json.loads("")
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

>>> open("empty.json", "w").close()
>>> json.load(open("empty.json"))
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

Fix: Check the string before parsing.

import json

raw = response.text  # or file.read()

if not raw or not raw.strip():
    raise ValueError("Response body is empty β€” nothing to parse")

data = json.loads(raw)

2. Server returned HTML instead of JSON

This is the cause that trips people up the most. Your code is working fine, but the server at that URL returned a 404 page, a login redirect, a Cloudflare challenge, or a maintenance page β€” all HTML. json.loads("<html>...")) fails immediately because < is not valid JSON.

>>> json.loads("<html><body>404 Not Found</body></html>")
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

Fix: Check the Content-Type header before parsing, and log the raw body when it fails so you can see what the server actually sent.

import json
import urllib.request

def fetch_json(url: str) -> dict:
    with urllib.request.urlopen(url) as resp:
        body = resp.read().decode("utf-8")
        content_type = resp.headers.get("Content-Type", "")

    if not body.strip():
        raise ValueError("Empty response body")

    if "text/html" in content_type:
        snippet = body[:300].replace("\n", " ")
        raise ValueError(
            f"Server returned HTML, not JSON.\n"
            f"Content-Type: {content_type}\n"
            f"Body starts with: {snippet!r}"
        )

    try:
        return json.loads(body)
    except json.JSONDecodeError as exc:
        snippet = body[:300].replace("\n", " ")
        raise ValueError(
            f"Invalid JSON from server.\n"
            f"Body starts with: {snippet!r}"
        ) from exc

If you use the requests library, the pattern is the same β€” use resp.headers["Content-Type"] and resp.text. You can also call resp.raise_for_status() before parsing so HTTP 4xx/5xx errors surface immediately rather than after the confused JSON parse attempt.

3. Whitespace-only string

A string of spaces, tabs, or newlines produces a slightly different position in the message but the same error class:

>>> json.loads("   ")
json.decoder.JSONDecodeError: Expecting value: line 1 column 4 (char 3)

The fix is the same as case 1: call .strip() and check the result before parsing.

4. UTF-8 BOM at the start of a file

Files saved by Windows tools (Excel "Save as UTF-8 CSV", Notepad, some editors) sometimes write a three-byte UTF-8 BOM (\xef\xbb\xbf) before the actual content. Python's json.load() sees that byte sequence as an invalid first character and raises a slightly different message:

json.decoder.JSONDecodeError: Unexpected UTF-8 BOM (decode using utf-8-sig): line 1 column 1 (char 0)

Python is actually helpful here β€” it tells you exactly what to do.

# Wrong β€” fails on BOM files
with open("export.json", encoding="utf-8") as f:
    data = json.load(f)
# json.decoder.JSONDecodeError: Unexpected UTF-8 BOM ...

# Right β€” utf-8-sig silently strips the BOM if present, harmless otherwise
with open("export.json", encoding="utf-8-sig") as f:
    data = json.load(f)

Using encoding="utf-8-sig" on all JSON files you read from disk is a safe habit: it strips the BOM when present and does nothing when it is absent.

Quick diagnostic checklist

When you hit this error, run through these in order:

  1. Print or log the raw string before parsing: print(repr(raw[:200])). This immediately tells you whether it is empty, HTML, or otherwise unexpected.
  2. If it is empty, find out why the source (API, file, database query) returned nothing.
  3. If it starts with <, you got HTML. Check the URL, authentication, and HTTP status code.
  4. If it starts with \xef\xbb\xbf (shows as a strange leading character), open the file with encoding="utf-8-sig".
  5. If it looks almost like JSON but fails, you may have a trailing comma or a comment β€” Python's json module follows strict RFC 8259 and rejects both. Use a lenient parser like json5 or clean the string first.

Putting it together: a reusable safe parse helper

import json
from typing import Any

def safe_json_loads(text: str, source: str = "response") -> Any:
    """
    Parse a JSON string with a clear error message when input is empty or not JSON.
    Raises ValueError with diagnostic detail instead of a bare JSONDecodeError.
    """
    if text is None:
        raise ValueError(f"{source} was None β€” check that the call succeeded")

    stripped = text.strip()
    if not stripped:
        raise ValueError(f"{source} body is empty")

    if stripped.startswith("<"):
        raise ValueError(
            f"{source} looks like HTML, not JSON. "
            f"First 200 chars: {text[:200]!r}"
        )

    try:
        return json.loads(stripped)
    except json.JSONDecodeError as exc:
        raise ValueError(
            f"Invalid JSON in {source}. "
            f"First 200 chars: {text[:200]!r}"
        ) from exc

Using this instead of bare json.loads() turns a cryptic "Expecting value: line 1 column 1" into an actionable message that shows you the actual content that was received.

How to confirm the fix worked

>>> safe_json_loads("")
ValueError: response body is empty

>>> safe_json_loads("<html></html>")
ValueError: response looks like HTML, not JSON. First 200 chars: '<html></html>'

>>> safe_json_loads('{"status": "ok"}')
{'status': 'ok'}

Tested on Ubuntu 24.04 with Python 3.12.3, GCC 13.3.0.

References

Python JSON error handling debugging
NanoTech Insight
Published by
NanoTech Insight
Independent technology site

This article was drafted with AI assistance and grounded in the references linked above. Technology changes quickly, so please verify details against official documentation before relying on them in production.

Related Articles

git fatal: refusing to merge unrelated histories
2026-10-09
Python sqlite3 OperationalError: database is locked, Fixed
2026-10-06
Git Detached HEAD: What It Means and How to Fix It
2026-10-02
Python subprocess.run vs Popen: Which One to Use
2026-09-29
← Back to Home