GEMINI LABJP
NANOLITE — Nano Banana 2 Lite is here: Google's fastest and most cost-efficient Gemini Image model, made for running lightweight image generation cheaplyOMNIFLASH — Gemini Omni Flash is in public preview, a natively multimodal model that lets enterprises and developers build custom, dynamic video workflowsAGENTS — Managed Agents expand with background: true for async server-side runs and polling, remote MCP server integration, and refreshing credentials across interactionsMEMORY — The Memory Bank IngestEvents API is generally available, decoupling event ingestion from memory generation so you can stream content continuouslyTHROUGHPUT — Provisioned Throughput now lets you submit up to seven pending orders for the same model and regionDEPRECATE — Image generation models shut down on August 17, and the Grok 4.1 family on the Gemini Enterprise Agent Platform on August 20NANOLITE — Nano Banana 2 Lite is here: Google's fastest and most cost-efficient Gemini Image model, made for running lightweight image generation cheaplyOMNIFLASH — Gemini Omni Flash is in public preview, a natively multimodal model that lets enterprises and developers build custom, dynamic video workflowsAGENTS — Managed Agents expand with background: true for async server-side runs and polling, remote MCP server integration, and refreshing credentials across interactionsMEMORY — The Memory Bank IngestEvents API is generally available, decoupling event ingestion from memory generation so you can stream content continuouslyTHROUGHPUT — Provisioned Throughput now lets you submit up to seven pending orders for the same model and regionDEPRECATE — Image generation models shut down on August 17, and the Grok 4.1 family on the Gemini Enterprise Agent Platform on August 20
Articles/API / SDK
API / SDK/2026-04-10Intermediate

How to Fix Gemini API JSON and Structured Output Errors

Troubleshoot Gemini API JSON Mode and Structured Output errors including malformed JSON, schema violations, and truncated responses with step-by-step solutions and code examples.

gemini-api277structured-output22json-mode2troubleshooting82error15fix10

Understanding Gemini API JSON and Structured Output Issues

Gemini API's JSON Mode and Structured Output are powerful features that let you receive AI responses in machine-parseable JSON format. However, when building production applications, you'll inevitably run into issues like unparseable JSON, schema violations, or truncated responses that break your data pipeline.

This guide covers the most common JSON output error patterns, explains why they happen, and walks you through concrete fixes with Python and JavaScript code examples. Whether you're dealing with malformed responses or mysterious empty outputs, you'll find a solution here.

The 5 Most Common JSON Output Error Patterns

Before diving into solutions, identify which problem you're facing. Gemini API structured output issues fall into five distinct categories.

Pattern 1: Response Isn't JSON at All

You've set response_mime_type to application/json, but the model returns plain text or Markdown instead. This is especially common when migrating from Gemini 1.5 to Gemini 2.5 series models.

Pattern 2: JSON Syntax Errors (Parse Failures)

The response looks like JSON but throws an exception when you call JSON.parse() or json.loads(). Common culprits include extra code block markers (```), trailing commas, or unescaped special characters.

Pattern 3: Schema Violations

The response is valid JSON, but it doesn't match your response_schema. Fields are missing, types are wrong (string instead of number), or enum values aren't respected.

Pattern 4: Truncated JSON (Incomplete Output)

When requesting large outputs, the JSON object ends abruptly without closing brackets. The finish_reason shows MAX_TOKENS, meaning the model ran out of output space before completing the structure.

Pattern 5: Empty or Null Responses

Despite having a valid schema, the API returns an empty string or null. This is typically caused by safety filters blocking the request before any JSON generation begins.

Root Cause Analysis: Why JSON Output Fails

Understanding the underlying causes helps you apply the right fix immediately.

Missing or Incorrect response_mime_type

The most fundamental cause is not setting response_mime_type correctly in your request configuration. SDK parameter names can vary between versions, so always check the latest documentation.

# ❌ Wrong: not including response_mime_type in generation config
model = genai.GenerativeModel("gemini-2.5-flash")
response = model.generate_content("Return user data as JSON")
 
# ✅ Correct: explicitly setting response_mime_type
model = genai.GenerativeModel(
    "gemini-2.5-flash",
    generation_config=genai.GenerationConfig(
        response_mime_type="application/json"
    )
)
response = model.generate_content("Return user data as JSON")
print(response.text)
# Expected: {"name": "John Doe", "age": 30, "email": "john@example.com"}

Incomplete Schema Definitions

When your response_schema is underspecified, the model fills in gaps with its own interpretation. This is the primary cause of schema violations.

# ❌ Insufficient schema (no type specifications)
schema = {"type": "object", "properties": {"score": {}}}
 
# ✅ Complete schema (types, required fields, and descriptions)
schema = {
    "type": "object",
    "properties": {
        "score": {
            "type": "number",
            "description": "Evaluation score from 0 to 100"
        },
        "category": {
            "type": "string",
            "enum": ["positive", "negative", "neutral"],
            "description": "Sentiment category"
        }
    },
    "required": ["score", "category"]
}

Insufficient max_output_tokens

The default token limit often isn't enough for complex JSON structures. This is the most common cause of truncated output, especially when generating arrays with many items.

Safety Filter Blocks

If the input content triggers safety policies, the model blocks the request before generating any JSON output, resulting in empty or null responses.

Step-by-Step Solutions

Step 1: Verify Your SDK and Model Version

Start by confirming you're using a current SDK version and a model that supports JSON Mode.

# Check and update Python SDK
pip install --upgrade google-genai
python -c "import google.genai; print(google.genai.__version__)"
// Verify Node.js SDK loads correctly
const { GoogleGenAI } = require("@google/genai");
console.log("SDK loaded successfully");
 
// Update to latest: npm install @google/genai@latest

Models with full JSON Mode and Structured Output support:

  • Gemini 2.5 Pro — Full support (recommended)
  • Gemini 2.5 Flash — Full support (recommended for cost efficiency)
  • Gemini 2.0 Flash — Supported
  • Gemini 1.5 Pro / Flash — Basic support (some limitations)

Step 2: Configure response_mime_type and response_schema Correctly

This is the single most important step for reliable JSON output.

import google.genai as genai
 
client = genai.Client(api_key="YOUR_API_KEY")
 
# Define a complete schema
product_schema = {
    "type": "object",
    "properties": {
        "products": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "name": {"type": "string", "description": "Product name"},
                    "price": {"type": "integer", "description": "Price in USD"},
                    "in_stock": {"type": "boolean", "description": "Availability"}
                },
                "required": ["name", "price", "in_stock"]
            }
        }
    },
    "required": ["products"]
}
 
response = client.models.generate_content(
    model="gemini-2.5-flash",
    contents="Generate info for 3 products: laptop, mouse, keyboard",
    config=genai.types.GenerateContentConfig(
        response_mime_type="application/json",
        response_schema=product_schema,
        max_output_tokens=2048
    )
)
 
import json
data = json.loads(response.text)
print(json.dumps(data, indent=2))
# Expected output:
# {
#   "products": [
#     {"name": "Laptop", "price": 899, "in_stock": true},
#     {"name": "Mouse", "price": 39, "in_stock": true},
#     {"name": "Keyboard", "price": 79, "in_stock": false}
#   ]
# }
const { GoogleGenAI } = require("@google/genai");
 
const ai = new GoogleGenAI({ apiKey: "YOUR_API_KEY" });
 
const productSchema = {
  type: "object",
  properties: {
    products: {
      type: "array",
      items: {
        type: "object",
        properties: {
          name: { type: "string", description: "Product name" },
          price: { type: "integer", description: "Price in USD" },
          in_stock: { type: "boolean", description: "Availability" }
        },
        required: ["name", "price", "in_stock"]
      }
    }
  },
  required: ["products"]
};
 
async function generateProducts() {
  const response = await ai.models.generateContent({
    model: "gemini-2.5-flash",
    contents: "Generate info for 3 products: laptop, mouse, keyboard",
    config: {
      responseMimeType: "application/json",
      responseSchema: productSchema,
      maxOutputTokens: 2048
    }
  });
  const data = JSON.parse(response.text);
  console.log(JSON.stringify(data, null, 2));
}
 
generateProducts();

Step 3: Increase max_output_tokens for Large Responses

If your JSON output is getting truncated, bump up the token limit.

config = genai.types.GenerateContentConfig(
    response_mime_type="application/json",
    response_schema=product_schema,
    max_output_tokens=8192  # Set higher than default
)

As a rough guide, each JSON character consumes about 1–2 tokens. For JSON responses with 1,000+ fields, set max_output_tokens to at least 8192.

Step 4: Implement Response Validation

Never trust model output blindly. Always validate the parsed JSON on your end.

import json
import re
 
def validate_and_parse(response_text, expected_keys=None):
    """Safe JSON parser for Gemini API responses"""
    text = response_text.strip()
 
    # Remove code block markers
    if text.startswith("```"):
        text = text.split("\n", 1)[1] if "\n" in text else text[3:]
    if text.endswith("```"):
        text = text[:-3]
    text = text.strip()
 
    try:
        data = json.loads(text)
    except json.JSONDecodeError as e:
        print(f"JSON parse error: {e}")
        # Attempt to fix trailing commas
        text = re.sub(r',\s*([}\]])', r'\1', text)
        try:
            data = json.loads(text)
            print("Recovered after removing trailing commas")
        except json.JSONDecodeError:
            raise ValueError(f"Unrecoverable JSON: {text[:200]}...")
 
    # Verify expected keys exist
    if expected_keys:
        missing = [k for k in expected_keys if k not in data]
        if missing:
            raise ValueError(f"Missing keys: {missing}")
 
    return data
 
# Usage
result = validate_and_parse(response.text, expected_keys=["products"])
print(f"Products received: {len(result['products'])}")

Step 5: Check finish_reason and Implement Retry Logic

Use finish_reason to detect incomplete responses and retry automatically.

def generate_with_retry(client, prompt, schema, max_retries=3):
    """Auto-retry based on finish_reason"""
    for attempt in range(max_retries):
        response = client.models.generate_content(
            model="gemini-2.5-flash",
            contents=prompt,
            config=genai.types.GenerateContentConfig(
                response_mime_type="application/json",
                response_schema=schema,
                max_output_tokens=8192
            )
        )
 
        candidate = response.candidates[0]
        finish_reason = candidate.finish_reason
 
        if finish_reason == "STOP":
            try:
                return json.loads(response.text)
            except json.JSONDecodeError:
                print(f"Attempt {attempt + 1}: JSON parse failed, retrying...")
                continue
 
        elif finish_reason == "MAX_TOKENS":
            print(f"Attempt {attempt + 1}: Output truncated, retrying...")
            continue
 
        elif finish_reason == "SAFETY":
            print(f"Attempt {attempt + 1}: Blocked by safety filter")
            raise ValueError("Safety filter blocked the response")
 
        else:
            print(f"Attempt {attempt + 1}: Unexpected: {finish_reason}")
            continue
 
    raise RuntimeError(f"Failed after {max_retries} retries")

Verification: Confirming the Fix Works

After applying the fixes, run these verification steps to confirm everything is working.

Test 1: Basic JSON output

test_response = client.models.generate_content(
    model="gemini-2.5-flash",
    contents="Generate a sample person with name and age",
    config=genai.types.GenerateContentConfig(
        response_mime_type="application/json",
        response_schema={
            "type": "object",
            "properties": {
                "name": {"type": "string"},
                "age": {"type": "integer"}
            },
            "required": ["name", "age"]
        }
    )
)
data = json.loads(test_response.text)
assert isinstance(data["name"], str), "name should be string"
assert isinstance(data["age"], int), "age should be integer"
print("✅ Basic JSON test passed")

Test 2: Large array output

Verify that arrays with many items don't get truncated.

large_response = client.models.generate_content(
    model="gemini-2.5-flash",
    contents="List all 50 US states with their capitals",
    config=genai.types.GenerateContentConfig(
        response_mime_type="application/json",
        response_schema={
            "type": "object",
            "properties": {
                "states": {
                    "type": "array",
                    "items": {
                        "type": "object",
                        "properties": {
                            "name": {"type": "string"},
                            "capital": {"type": "string"}
                        },
                        "required": ["name", "capital"]
                    }
                }
            },
            "required": ["states"]
        },
        max_output_tokens=8192
    )
)
data = json.loads(large_response.text)
print(f"✅ Large data test: {len(data['states'])} states returned")

Test 3: Confirm finish_reason

candidate = large_response.candidates[0]
assert candidate.finish_reason == "STOP", f"Unexpected: {candidate.finish_reason}"
print("✅ finish_reason is STOP (complete response)")

Prevention: Best Practices to Avoid Recurrence

Always Define Complete Schemas

Specify type, description, and required for every field. Ambiguity leads to unpredictable model behavior and inconsistent outputs.

Build Validation into Production Code

AI model outputs are inherently non-deterministic. Always implement try/catch blocks with type checking, and have fallback handling for parse failures.

Chunk Large Outputs

Instead of requesting 50 items in one call, split your requests into batches of 10–15 items each. This dramatically reduces the risk of truncation.

Reinforce with System Instructions

In addition to response_mime_type and response_schema, add System Instructions stating "Return only valid JSON. Do not wrap the response in code blocks." This further reduces the rate of malformed output.

Keep Your SDK Updated

The Google AI Python SDK receives frequent updates with bug fixes for structured output. Run pip install --upgrade google-genai regularly to stay current.

Looking back

Most Gemini API JSON output issues come down to three things: correctly configuring response_mime_type and response_schema, setting sufficient max_output_tokens, and implementing proper response validation. The five error patterns and solutions covered in this guide should resolve the vast majority of structured output problems you'll encounter in production.

For broader error handling strategies, see our Gemini API Error Handling Complete Guide. If you're new to JSON Mode, start with our JSON Mode and Structured Output guide.

To build a solid foundation in API design and error handling,

Share

Thank You for Reading

Gemini Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

API / SDK2026-05-31
Why Gemini API Throws 'Unsupported MIME type' and How to Fix It
The 'Unsupported MIME type' error from the Gemini API has three distinct causes: a misspelled MIME string, an octet-stream upload, and a genuinely unsupported format. Here is how to tell them apart with code that actually works.
API / SDK2026-05-30
Why Gemini 2.5 Pro Rejects thinkingBudget: 0 (and How to Fix It)
Setting thinkingBudget to 0 on Gemini 2.5 Pro returns a 400 INVALID_ARGUMENT error. Here is why the per-model thinking budget ranges differ, how to minimize thinking on Pro the right way, and when to switch to Flash, with Python and JavaScript examples.
API / SDK2026-05-23
Why Your Gemini API Structured Output Keeps Failing Validation — and How to Stabilize It
A field guide to the three layers where Gemini API structured output breaks — server-side schema rejection, silent empty responses, and client-side parsing — with practical fixes from an indie developer's production AdMob reporting pipeline.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →