Groovy JSON Parsing with JsonSlurper – 10+ Tested Examples

Groovy JSON parsing with JsonSlurper. 10+ examples covering parseText, parse URL, nested fields, arrays, lazy parsing, and error handling on Groovy 5.0.6.

“JSON is the duct tape of the internet. Groovy’s JsonSlurper is the scissors that makes it easy to cut through.”

Last Updated: July 2026 | Tested on: Groovy 5.0.6, Java 17+ | Difficulty: Beginner to Intermediate | Reading Time: 22 minutes

If you work with web APIs, configuration files, or any modern data exchange format, you work with JSON. And if you’re writing Groovy, you have one of the cleanest JSON parsing experiences available in any JVM language – thanks to JsonSlurper.

This Groovy JSON parsing guide walks you through what you need for working with JsonSlurper. We’ll cover parsing strings, files, and URLs, accessing nested fields, handling arrays, type coercion, lazy vs non-lazy parsing, and reliable error handling – all with tested examples and real output.

Once you’ve mastered parsing, head over to our Groovy JSON Output with JsonBuilder guide to learn how to generate JSON. And if you want to consume REST APIs end-to-end, check out Groovy REST API Consumption.

The diagram below shows how JsonSlurper decides which native Groovy type each JSON value becomes, so you can see at a glance why an object turns into a Map, an array into a List, and why numbers split into Integer, Long, or BigDecimal:

object { }array [ ]numbertrue or falsequoted textnullno, fits int rangeno, fits a longno, bigger than longyesJSON source(string, file, URL, stream)JsonSlurper.parseJSON value kind?Map (LazyMap)List (ArrayList)has decimal orexponent?BooleanStringnullIntegerLongBigIntegerBigDecimal (exact)Groovy JsonSlurper Type Mapping: How JSON Values Become Groovy Types

Tip: click the diagram to open it full screen, then use the zoom and pan controls for a closer look.

What Is JsonSlurper?

groovy.json.JsonSlurper is Groovy’s built-in JSON parser. It takes a JSON string (or stream, or URL) and converts it into native Groovy data structures – maps for JSON objects and lists for JSON arrays. No third-party libraries needed. No annotations. No POJOs. Just parse and go.

According to the official Groovy JSON documentation, JsonSlurper parses text or reader content into Groovy data structures such as maps, lists, and primitive types like Integer, Long, BigDecimal, and String.

Key Points:

  • Part of the groovy.json package – no external dependencies
  • Parses JSON objects into Map instances and JSON arrays into List instances
  • Supports multiple parser types: CHAR_BUFFER (default), INDEX_OVERLAY, LAX, and CHARACTER_SOURCE
  • Handles JSON numbers as Integer, Long, or BigDecimal depending on size
  • JSON booleans become Groovy Boolean values and null becomes Groovy null
  • Thread-safe – you can reuse a single instance across threads

Why Use JsonSlurper for Groovy JSON Parsing?

You might wonder why you’d pick JsonSlurper over Jackson, Gson, or other popular JSON libraries. Here’s why:

  • Zero setup – it ships with Groovy, no Gradle or Maven dependency needed
  • Dynamic access – parsed JSON becomes maps and lists, so you use dot notation and array indexing directly
  • No POJOs required – you don’t need to define classes that mirror the JSON structure
  • Groovy-native types – numbers, booleans, and strings map directly to Groovy types
  • Multiple parser modes – pick the right parser for your performance and correctness needs
  • Great for scripting – perfect for quick scripts, CI/CD pipelines, and Jenkinsfiles

That said, if you need schema validation, custom deserializers, or streaming parsing for very large files, Jackson is still a solid choice. But for 90% of Groovy JSON work, JsonSlurper is all you need.

Basic Syntax

The basic pattern for Groovy JSON parsing is always the same: create a JsonSlurper instance, call a parse method, and work with the result.

JsonSlurper Basic Syntax

import groovy.json.JsonSlurper

def slurper = new JsonSlurper()

// Parse a JSON string
def result = slurper.parseText('{"name": "Alice", "age": 30}')

// Access fields with dot notation
println result.name  // Alice
println result.age   // 30

The main parse methods available are:

MethodInput SourceUse Case
parseText(String)JSON stringInline JSON, API responses stored as strings
parse(Reader)ReaderFiles, streams
parse(InputStream)Input streamHTTP connections, resources
parse(File)File objectLocal JSON files
parse(URL)URL objectRemote JSON endpoints
parse(byte[])Byte arrayBinary data, network buffers

10+ Practical Examples

Example 1: Parse a Simple JSON Object

What we’re doing: Parsing a basic JSON object string and accessing its properties using dot notation.

Example 1: Simple JSON Object

import groovy.json.JsonSlurper

def slurper = new JsonSlurper()
def json = slurper.parseText('''
{
    "name": "Alice",
    "age": 30,
    "active": true,
    "email": "alice@example.com"
}
''')

println json.name      // Dot notation
println json['age']    // Bracket notation
println json.active
println json.email
println json.getClass().name

Output

Alice
30
true
alice@example.com
org.apache.groovy.json.internal.LazyMap

What happened here: parseText() converted the JSON string into a LazyMap. You can access fields using dot notation (like a POJO) or bracket notation (like a map). The JSON boolean true became a Groovy Boolean, and the number 30 became an Integer.

Example 2: Parse Nested JSON Objects

What we’re doing: Navigating deeply nested JSON structures using chained dot notation.

Example 2: Nested JSON

import groovy.json.JsonSlurper

def slurper = new JsonSlurper()
def json = slurper.parseText('''
{
    "user": {
        "name": "Bob",
        "address": {
            "street": "123 Main St",
            "city": "Springfield",
            "state": "IL",
            "zip": "62701"
        },
        "preferences": {
            "theme": "dark",
            "notifications": {
                "email": true,
                "sms": false,
                "push": true
            }
        }
    }
}
''')

println json.user.name
println json.user.address.city
println json.user.address.zip
println json.user.preferences.theme
println json.user.preferences.notifications.email
println json.user.preferences.notifications.sms

Output

Bob
Springfield
62701
dark
true
false

What happened here: Each nested JSON object became a nested map. You simply chain dot notation to drill into the structure – json.user.preferences.notifications.email. No need for get() calls or casting. Groovy’s dynamic nature makes this incredibly readable.

Example 3: Parse JSON Arrays

What we’re doing: Parsing JSON arrays and iterating through them with Groovy collection methods.

Example 3: JSON Arrays

import groovy.json.JsonSlurper

def slurper = new JsonSlurper()

// Top-level JSON array
def colors = slurper.parseText('["red", "green", "blue", "yellow"]')
println "Colors: ${colors}"
println "First:  ${colors[0]}"
println "Last:   ${colors[-1]}"
println "Count:  ${colors.size()}"

// Array of objects
def users = slurper.parseText('''
[
    {"name": "Alice", "role": "admin"},
    {"name": "Bob", "role": "editor"},
    {"name": "Charlie", "role": "viewer"}
]
''')

users.each { user ->
    println "${user.name} is a ${user.role}"
}

// Extract just the names
def names = users.collect { it.name }
println "Names: ${names}"

Output

Colors: [red, green, blue, yellow]
First:  red
Last:   yellow
Count:  4
Alice is a admin
Bob is a editor
Charlie is a viewer
Names: [Alice, Bob, Charlie]

What happened here: A top-level JSON array becomes a Groovy List. When the array contains objects, each object becomes a map. You can use all the Groovy collection methods – each(), collect(), find(), findAll() – directly on the parsed result.

Example 4: Accessing Nested Arrays

What we’re doing: Working with JSON that contains arrays nested inside objects.

Example 4: Nested Arrays

import groovy.json.JsonSlurper

def slurper = new JsonSlurper()
def json = slurper.parseText('''
{
    "company": "TechCorp",
    "departments": [
        {
            "name": "Engineering",
            "employees": ["Alice", "Bob", "Charlie"]
        },
        {
            "name": "Marketing",
            "employees": ["Diana", "Eve"]
        },
        {
            "name": "Sales",
            "employees": ["Frank", "Grace", "Heidi", "Ivan"]
        }
    ]
}
''')

println "Company: ${json.company}"
println "Departments: ${json.departments.size()}"

// Access specific department
println "First dept: ${json.departments[0].name}"
println "Eng team:   ${json.departments[0].employees}"

// Spread operator to get all department names
println "All depts:  ${json.departments*.name}"

// Flatten all employees
def allEmployees = json.departments.collectMany { it.employees }
println "All staff:  ${allEmployees}"
println "Headcount:  ${allEmployees.size()}"

Output

Company: TechCorp
Departments: 3
First dept: Engineering
Eng team:   [Alice, Bob, Charlie]
All depts:  [Engineering, Marketing, Sales]
All staff:  [Alice, Bob, Charlie, Diana, Eve, Frank, Grace, Heidi, Ivan]
Headcount:  9

What happened here: The spread operator (*.) extracts a property from each element in a list. The collectMany() method flattens nested lists into one – perfect for gathering all items from multiple nested arrays.

Example 5: Type Coercion After Parsing

What we’re doing: Checking and coercing the types that JsonSlurper produces from different JSON value types.

Example 5: Type Coercion

import groovy.json.JsonSlurper

def slurper = new JsonSlurper()
def json = slurper.parseText('''
{
    "intValue": 42,
    "longValue": 9999999999,
    "decimalValue": 3.14159,
    "boolValue": true,
    "nullValue": null,
    "stringValue": "hello",
    "stringNumber": "42"
}
''')

println "int:     ${json.intValue}     -> ${json.intValue.getClass().name}"
println "long:    ${json.longValue}    -> ${json.longValue.getClass().name}"
println "decimal: ${json.decimalValue} -> ${json.decimalValue.getClass().name}"
println "bool:    ${json.boolValue}    -> ${json.boolValue.getClass().name}"
println "null:    ${json.nullValue}    -> ${json.nullValue == null}"
println "string:  ${json.stringValue}  -> ${json.stringValue.getClass().name}"

// Convert string to integer manually
def num = json.stringNumber as Integer
println "coerced: ${num} -> ${num.getClass().name}"

// Use Groovy's safe navigation for potentially null fields
println "safe:    ${json.nullValue?.toUpperCase()}"
println "missing: ${json.nonExistent}"

Output

int:     42     -> java.lang.Integer
long:    9999999999    -> java.lang.Long
decimal: 3.14159 -> java.math.BigDecimal
bool:    true    -> java.lang.Boolean
null:    null    -> true
string:  hello  -> java.lang.String
coerced: 42 -> java.lang.Integer
safe:    null
missing: null

What happened here: JsonSlurper automatically picks the right type. Small integers become Integer, large ones become Long, and decimals become BigDecimal (not Double – so you get exact precision). Accessing a missing key returns null rather than throwing an exception.

Example 6: Parse JSON from a File

What we’re doing: Reading and parsing a JSON file from the local filesystem.

Example 6: Parse JSON File

import groovy.json.JsonSlurper

// Create a sample JSON file first
def jsonContent = '''
{
    "database": {
        "host": "localhost",
        "port": 5432,
        "name": "myapp_db",
        "credentials": {
            "username": "admin",
            "password": "secret123"
        }
    }
}
'''
def tempFile = File.createTempFile('config', '.json')
tempFile.text = jsonContent
tempFile.deleteOnExit()

// Parse the file
def slurper = new JsonSlurper()
def config = slurper.parse(tempFile)

println "Host: ${config.database.host}"
println "Port: ${config.database.port}"
println "DB:   ${config.database.name}"
println "User: ${config.database.credentials.username}"

// You can also parse using a Reader
def config2 = slurper.parse(new FileReader(tempFile))
println "Via Reader - Host: ${config2.database.host}"

Output

Host: localhost
Port: 5432
DB:   myapp_db
User: admin
Via Reader - Host: localhost

What happened here: The parse(File) method reads and parses in one call. You don’t need to read the file content separately. You can also use parse(Reader) if you need more control over encoding or buffering.

Example 7: Parse JSON from a URL

What we’re doing: Fetching and parsing JSON directly from a URL (useful for simple API calls).

Example 7: Parse JSON from URL

import groovy.json.JsonSlurper

def slurper = new JsonSlurper()

// Parse directly from a URL
// def result = slurper.parse(new URL('https://api.example.com/data'))

// Simulating a URL parse with parseText for demonstration
def json = slurper.parseText('''
{
    "userId": 1,
    "id": 1,
    "title": "Sample Todo",
    "completed": false
}
''')

println "ID:        ${json.id}"
println "Title:     ${json.title}"
println "Completed: ${json.completed}"

// Alternative: read URL content first, then parse
// def text = new URL('https://api.example.com/data').text
// def result = slurper.parseText(text)

// Using URL.text with JsonSlurper (the most Groovy way)
// def todo = slurper.parseText('https://jsonplaceholder.typicode.com/todos/1'.toURL().text)
// println todo.title

Output

ID:        1
Title:     Sample Todo
Completed: false

What happened here: JsonSlurper can parse directly from a URL object. Alternatively, you can use Groovy’s URL.text property to fetch the content first, then pass it to parseText(). For more advanced HTTP handling, see our Groovy REST API Consumption guide.

Example 8: Filtering and Searching Parsed JSON

What we’re doing: Using Groovy collection methods to filter, search, and transform parsed JSON data.

Example 8: Filter and Search JSON

import groovy.json.JsonSlurper

def slurper = new JsonSlurper()
def products = slurper.parseText('''
[
    {"name": "Laptop",    "price": 999.99,  "category": "electronics", "inStock": true},
    {"name": "Book",      "price": 14.99,   "category": "education",   "inStock": true},
    {"name": "Phone",     "price": 699.99,  "category": "electronics", "inStock": false},
    {"name": "Tablet",    "price": 449.99,  "category": "electronics", "inStock": true},
    {"name": "Notebook",  "price": 4.99,    "category": "education",   "inStock": true},
    {"name": "Monitor",   "price": 349.99,  "category": "electronics", "inStock": false}
]
''')

// Find all electronics in stock
def available = products.findAll { it.category == 'electronics' && it.inStock }
println "Available electronics:"
available.each { println "  ${it.name} - \$${it.price}" }

// Find the cheapest product
def cheapest = products.min { it.price }
println "\nCheapest: ${cheapest.name} (\$${cheapest.price})"

// Total value of in-stock items
def totalValue = products.findAll { it.inStock }.sum { it.price }
println "In-stock value: \$${totalValue}"

// Group by category
def grouped = products.groupBy { it.category }
grouped.each { category, items ->
    println "\n${category}: ${items*.name}"
}

Output

Available electronics:
  Laptop - $999.99
  Tablet - $449.99

Cheapest: Notebook ($4.99)
In-stock value: $1469.96

electronics: [Laptop, Phone, Tablet, Monitor]

education: [Book, Notebook]

What happened here: Once JSON is parsed into maps and lists, you have the full power of Groovy’s collection methods at your disposal. findAll() filters, min() finds the smallest by a given criteria, sum() aggregates, and groupBy() categorizes. This is one of the biggest advantages of Groovy’s approach to JSON.

Example 9: Parse JSON with Special Characters and Unicode

What we’re doing: Handling JSON that contains escape sequences, Unicode characters, and special strings.

Example 9: Special Characters

import groovy.json.JsonSlurper

def slurper = new JsonSlurper()
def json = slurper.parseText('''
{
    "escaped_quotes": "She said \\"hello\\"",
    "newline": "line1\\nline2",
    "tab": "col1\\tcol2",
    "unicode": "caf\\u00e9",
    "emoji": "Hello \\ud83d\\ude00",
    "backslash": "C:\\\\Users\\\\test",
    "empty": "",
    "spaces": "  trimmed  "
}
''')

println "Quotes:    ${json.escaped_quotes}"
println "Newline:   ${json.newline}"
println "Tab:       ${json.tab}"
println "Unicode:   ${json.unicode}"
println "Emoji:     ${json.emoji}"
println "Backslash: ${json.backslash}"
println "Empty:     '${json.empty}'"
println "Spaces:    '${json.spaces}'"
println "Trimmed:   '${json.spaces.trim()}'"

Output

Quotes:    She said "hello"
Newline:   line1
line2
Tab:       col1	col2
Unicode:   café
Emoji:     Hello 😀
Backslash: C:\Users\test
Empty:     ''
Spaces:    '  trimmed  '
Trimmed:   'trimmed'

What happened here: JsonSlurper correctly handles all JSON escape sequences including \", \n, \t, \\, and \uXXXX Unicode escapes. The parsed strings are regular Groovy strings, so you can apply any string methods like trim() or toUpperCase().

Example 10: Error Handling During JSON Parsing

What we’re doing: Gracefully handling malformed JSON and missing fields.

Example 10: Error Handling

import groovy.json.JsonSlurper
import groovy.json.JsonException

def slurper = new JsonSlurper()

// Handle malformed JSON
def badJsonSamples = [
    '{"name": "Alice", "age": }',           // Missing value
    '{"name": "Alice" "age": 30}',           // Missing comma
    '{name: "Alice"}',                        // Unquoted key (invalid in strict mode)
    'not json at all',                        // Not JSON
    ''                                        // Empty string
]

badJsonSamples.each { sample ->
    try {
        def result = slurper.parseText(sample)
        println "Parsed: ${result}"
    } catch (JsonException e) {
        println "JsonException: ${e.message.take(60)}..."
    } catch (Exception e) {
        println "${e.class.simpleName}: ${e.message?.take(60)}..."
    }
}

// Safe field access with defaults
println "\n--- Safe field access ---"
def json = slurper.parseText('{"name": "Alice"}')
println "Name:    ${json.name ?: 'Unknown'}"
println "Age:     ${json.age ?: 'Not specified'}"
println "Email:   ${json.email ?: 'N/A'}"

// Safe nested access
def config = slurper.parseText('{"db": {}}')
println "Host:    ${config.db?.host ?: 'localhost'}"
println "Port:    ${config.db?.port ?: 5432}"

Output

JsonException: Unable to determine the current character, it is not a strin...
JsonException: expecting '}' or ',' but got current char '"' with an int va...
JsonException: expecting '}' or ',' but got current char 'n' with an int va...
JsonException: null not parse properly

The current character read is 'n' w...
IllegalArgumentException: Text must not be null or empty...

--- Safe field access ---
Name:    Alice
Age:     Not specified
Email:   N/A
Host:    localhost
Port:    5432

What happened here: Malformed JSON throws JsonException. Always wrap parse calls in try-catch when dealing with external data. For missing fields, use the Elvis operator (?:) for defaults and safe navigation (?.) for nested access. Note that unquoted keys also fail with the default parser. If you need to accept them, switch to the LAX parser shown in Example 11. Also notice the fourth message prints across two lines. JsonSlurper builds a multi-line diagnostic for that case, so even the first 60 characters we keep with take(60) include a line break.

Example 11: Parse JSON with Different Parser Types

What we’re doing: Exploring the different parser types available in JsonSlurper and when to use each one.

Example 11: Parser Types

import groovy.json.JsonSlurper
import groovy.json.JsonParserType

def jsonText = '{"name": "Alice", "scores": [95, 87, 92]}'

// INDEX_OVERLAY - fast overlay parser, good for most cases
def slurper1 = new JsonSlurper().setType(JsonParserType.INDEX_OVERLAY)
def result1 = slurper1.parseText(jsonText)
println "INDEX_OVERLAY: ${result1.name}, scores=${result1.scores}"

// CHARACTER_SOURCE - best for large files from streams
def slurper2 = new JsonSlurper().setType(JsonParserType.CHARACTER_SOURCE)
def result2 = slurper2.parseText(jsonText)
println "CHAR_SOURCE:   ${result2.name}, scores=${result2.scores}"

// LAX - allows comments and unquoted keys
def slurper3 = new JsonSlurper().setType(JsonParserType.LAX)
def laxJson = '''
{
    // This is a comment (not valid in strict JSON!)
    name: "Bob",
    age: 25,
    'nickname': 'Bobby'
}
'''
def result3 = slurper3.parseText(laxJson)
println "LAX:           ${result3.name}, age=${result3.age}, nick=${result3.nickname}"

// CHAR_BUFFER - uses char buffer internally
def slurper4 = new JsonSlurper().setType(JsonParserType.CHAR_BUFFER)
def result4 = slurper4.parseText(jsonText)
println "CHAR_BUFFER:   ${result4.name}, scores=${result4.scores}"

Output

INDEX_OVERLAY: Alice, scores=[95, 87, 92]
CHAR_SOURCE:   Alice, scores=[95, 87, 92]
LAX:           Bob, age=25, nick=Bobby
CHAR_BUFFER:   Alice, scores=[95, 87, 92]

What happened here: The LAX parser is the most forgiving – it accepts comments, unquoted keys, and single-quoted strings. INDEX_OVERLAY is the fastest for most use cases. Use CHARACTER_SOURCE when parsing from large streams or readers.

Example 12: Real-World Example – Processing an API Response

What we’re doing: Simulating a real API response and extracting meaningful data from a complex JSON structure.

Example 12: Real-World API Response

import groovy.json.JsonSlurper

def slurper = new JsonSlurper()
def response = slurper.parseText('''
{
    "status": "success",
    "data": {
        "page": 1,
        "total_pages": 5,
        "per_page": 3,
        "total": 15,
        "results": [
            {
                "id": 101,
                "title": "Introduction to Groovy",
                "author": {"name": "Jane Doe", "email": "jane@example.com"},
                "tags": ["groovy", "jvm", "tutorial"],
                "published": true,
                "views": 15420
            },
            {
                "id": 102,
                "title": "Advanced JSON Parsing",
                "author": {"name": "John Smith", "email": "john@example.com"},
                "tags": ["groovy", "json", "advanced"],
                "published": true,
                "views": 8930
            },
            {
                "id": 103,
                "title": "Draft: Groovy DSLs",
                "author": {"name": "Jane Doe", "email": "jane@example.com"},
                "tags": ["groovy", "dsl"],
                "published": false,
                "views": 0
            }
        ]
    }
}
''')

// Check response status
if (response.status == 'success') {
    def data = response.data
    println "Page ${data.page} of ${data.total_pages} (${data.total} total articles)"
    println "-" * 50

    // Only published articles
    def published = data.results.findAll { it.published }
    published.each { article ->
        println "  [#${article.id}] ${article.title}"
        println "    By: ${article.author.name}"
        println "    Tags: ${article.tags.join(', ')}"
        println "    Views: ${article.views}"
        println()
    }

    // Most viewed article
    def topArticle = data.results.max { it.views }
    println "Most viewed: '${topArticle.title}' (${topArticle.views} views)"

    // All unique tags
    def allTags = data.results.collectMany { it.tags }.unique().sort()
    println "All tags: ${allTags}"
}

Output

Page 1 of 5 (15 total articles)
--------------------------------------------------
  [#101] Introduction to Groovy
    By: Jane Doe
    Tags: groovy, jvm, tutorial
    Views: 15420

  [#102] Advanced JSON Parsing
    By: John Smith
    Tags: groovy, json, advanced
    Views: 8930

Most viewed: 'Introduction to Groovy' (15420 views)
All tags: [advanced, dsl, groovy, json, jvm, tutorial]

What happened here: This is a realistic scenario – parsing a paginated API response with nested objects, arrays, and metadata. We checked the status, filtered for published articles, extracted nested author names, flattened all tags, and found the most popular article. This is where Groovy JsonSlurper truly shines – doing all of this in a few lines of readable code.

Lazy vs Non-Lazy Parsing

One Groovy JSON parsing detail that surprises people: by default, JsonSlurper creates lazy maps. This means values aren’t fully parsed until you actually access them. This is great for performance when you only need a subset of the data, but it can cause unexpected behavior in some cases.

Lazy vs Non-Lazy Parsing

import groovy.json.JsonSlurper
import groovy.json.JsonParserType

def jsonText = '{"name": "Alice", "age": 30, "scores": [95, 87, 92]}'

// Default lazy parsing
def lazySlurper = new JsonSlurper()
def lazyResult = lazySlurper.parseText(jsonText)
println "Lazy map type:  ${lazyResult.getClass().name}"

// CHARACTER_SOURCE parser (you might expect an eager map here)
def eagerSlurper = new JsonSlurper().setType(JsonParserType.CHARACTER_SOURCE)
def eagerResult = eagerSlurper.parseText(jsonText)
println "Eager map type: ${eagerResult.getClass().name}"

// Both produce identical results for reading
println "Lazy name:  ${lazyResult.name}"
println "Eager name: ${eagerResult.name}"

// But lazy maps are NOT thread-safe for the first access
// If sharing parsed data across threads, convert to a regular map:
def safeMap = new HashMap(lazyResult)
println "Safe map type: ${safeMap.getClass().name}"
println "Safe name:     ${safeMap.name}"

Output

Lazy map type:  org.apache.groovy.json.internal.LazyMap
Eager map type: org.apache.groovy.json.internal.LazyMap
Lazy name:  Alice
Eager name: Alice
Safe map type: java.util.HashMap
Safe name:     Alice

Surprised by that second line? So were we. You might expect CHARACTER_SOURCE to hand back a plain LinkedHashMap, but it does not. We ran this on Groovy 5.0.6 (the version at the time of writing) and on Groovy 4.0.24, and every parser type returns a lazy map class. Copying the result yourself, like the HashMap line above, is the reliable way to get a regular map.

The bottom line: if you’re parsing JSON in a single thread and reading it immediately, the default lazy parser is fine and faster. If you need to store the result for later use or share it across threads, wrap the result in a new HashMap(lazyResult) or new LinkedHashMap(lazyResult).

Edge Cases and Best Practices

Edge Case: Duplicate Keys

Duplicate Keys

import groovy.json.JsonSlurper

def slurper = new JsonSlurper()

// JSON with duplicate keys - last value wins
def json = slurper.parseText('{"name": "Alice", "name": "Bob"}')
println "Name: ${json.name}"  // Bob - last value wins

// Empty JSON object and array
def emptyObj = slurper.parseText('{}')
def emptyArr = slurper.parseText('[]')
println "Empty obj: ${emptyObj} (size: ${emptyObj.size()})"
println "Empty arr: ${emptyArr} (size: ${emptyArr.size()})"

// Deeply nested JSON (stress test)
def deep = slurper.parseText('{"a":{"b":{"c":{"d":{"e":"deep value"}}}}}')
println "Deep: ${deep.a.b.c.d.e}"

Output

Name: Bob
Empty obj: [:] (size: 0)
Empty arr: [] (size: 0)
Deep: deep value

Best Practices

DO:

  • Reuse JsonSlurper instances – they are thread-safe
  • Use ?. (safe navigation) and ?: (Elvis operator) for optional fields
  • Wrap external JSON parsing in try-catch blocks
  • Use LAX parser type when dealing with non-standard JSON (e.g., JSON with comments)
  • Convert lazy maps to regular maps when storing results long-term

DON’T:

  • Assume all JSON numbers are Integer – large numbers become Long or BigDecimal
  • Share lazy-parsed results across threads without converting first
  • Catch Exception when JsonException is more specific
  • Parse very large JSON files (100MB+) in one go – use a streaming parser instead

Common Pitfalls

Pitfall 1: Lazy Map Mutation

Problem: Trying to modify a lazy map and getting unexpected results.

Pitfall 1: Lazy Map Issues

import groovy.json.JsonSlurper

def slurper = new JsonSlurper()
def json = slurper.parseText('{"name": "Alice", "age": 30}')

// This works - but the result is a LazyMap
json.email = "alice@example.com"
println json  // May have unexpected ordering

// Fix: convert to a regular map first
def map = new LinkedHashMap(slurper.parseText('{"name": "Bob", "age": 25}'))
map.email = "bob@example.com"
println map

Output

[name:Alice, age:30, email:alice@example.com]
[name:Bob, age:25, email:bob@example.com]

Solution: If you plan to modify parsed JSON, wrap the result in a new LinkedHashMap() or new HashMap() first to get a standard mutable map.

Pitfall 2: Number Type Assumptions

Problem: Assuming all JSON integers parse to int.

Pitfall 2: Number Types

import groovy.json.JsonSlurper

def slurper = new JsonSlurper()
def json = slurper.parseText('''
{
    "small": 42,
    "big": 9999999999999,
    "decimal": 19.99,
    "scientific": 1.5e10
}
''')

println "${json.small}    -> ${json.small.getClass().name}"
println "${json.big}      -> ${json.big.getClass().name}"
println "${json.decimal}  -> ${json.decimal.getClass().name}"
println "${json.scientific} -> ${json.scientific.getClass().name}"

// This can cause issues if you explicitly cast
// int x = json.big  // This would overflow!
long safe = json.big as long
println "Safe long: ${safe}"

Output

42    -> java.lang.Integer
9999999999999      -> java.lang.Long
19.99  -> java.math.BigDecimal
1.5E+10 -> java.math.BigDecimal
Safe long: 9999999999999

Solution: Never assume the numeric type. Use as long, as BigDecimal, or check the type with instanceof if type precision matters.

Pitfall 3: Null vs Missing Key

Problem: Not distinguishing between a field that is explicitly null and a field that is missing.

Pitfall 3: Null vs Missing

import groovy.json.JsonSlurper

def slurper = new JsonSlurper()
def json = slurper.parseText('{"name": "Alice", "email": null}')

// Both return null - but for different reasons!
println "email:   ${json.email}"       // null - field exists but value is null
println "phone:   ${json.phone}"       // null - field does not exist

// How to distinguish:
println "has email? ${json.containsKey('email')}"   // true
println "has phone? ${json.containsKey('phone')}"   // false

Output

email:   null
phone:   null
has email? true
has phone? false

Solution: Use containsKey() to check whether a field exists in the parsed JSON, even if its value is null.

Conclusion

Groovy JSON parsing with JsonSlurper is one of the cleanest JSON parsing experiences on the JVM. No annotations, no POJOs, no configuration – just parse and go. It handles config files, API responses, and data pipelines equally well – JsonSlurper turns JSON into native Groovy data structures that you can slice, dice, filter, and transform with all the collection methods you already know.

The key is understanding the type mappings (objects become maps, arrays become lists, numbers become Integer/Long/BigDecimal), knowing when to use the LAX parser for non-standard JSON, and handling lazy maps correctly in multithreaded scenarios.

Now that you can parse JSON, the next step is generating it. Head over to our Groovy JSON Output with JsonBuilder guide to learn how to build JSON from Groovy objects, maps, and custom structures. And for end-to-end API work, check out Groovy REST API Consumption.

Summary

  • JsonSlurper is built into Groovy – no external dependencies needed
  • JSON objects parse to maps, JSON arrays parse to lists – use dot notation and indexing
  • Use parseText() for strings, parse(File) for files, parse(URL) for remote JSON
  • Numbers map to Integer, Long, or BigDecimal – never assume int
  • Use LAX parser for non-standard JSON with comments or unquoted keys
  • Convert lazy maps to regular maps when sharing across threads or storing long-term

If you also work with build tools, CI/CD pipelines, or cloud CLIs, check out Command Playground to practice 105+ CLI tools directly in your browser — no install needed.

Up next: Groovy JSON Output with JsonBuilder

Frequently Asked Questions

What is JsonSlurper in Groovy?

JsonSlurper is the class that handles Groovy JSON parsing out of the box, from the groovy.json package. It parses JSON strings, files, URLs, and streams into native Groovy data structures – maps for JSON objects and lists for JSON arrays. No external libraries are needed, and you access parsed fields using dot notation or bracket notation.

How do I parse a JSON string in Groovy?

Create a JsonSlurper instance and call parseText(): def json = new JsonSlurper().parseText('{"name": "Alice"}') . Then access fields with json.name. You can also parse files with parse(File), URLs with parse(URL), and streams with parse(InputStream).

What types does JsonSlurper return for JSON numbers?

JsonSlurper maps JSON integers to java.lang.Integer for small values and java.lang.Long for larger values. Decimal numbers become java.math.BigDecimal, which provides exact precision. Never assume all JSON numbers will be int – always check or cast explicitly when type precision matters.

What is the difference between lazy and non-lazy JSON parsing in Groovy?

The default parser creates lazy maps (LazyMap) where values aren’t fully materialized until accessed. This is faster when you only need a subset of the data. In our tests on Groovy 4 and 5, every parser type returns a lazy map class, so copy the result into a regular map with new HashMap(result) when sharing results across threads or storing them long-term.

How do I handle malformed JSON in Groovy?

Wrap your parseText() or parse() call in a try-catch block catching groovy.json.JsonException. For missing fields, use the Elvis operator (json.field ?: 'default') and safe navigation (json?.nested?.field). Use containsKey() to distinguish between a field that is explicitly null and a field that is missing entirely.

Interview Questions on Groovy JSON Parsing

“Interviewers rarely ask for definitions. They ask what happens in situations like these.”

Q: A Jenkins pipeline parses an API response with JsonSlurper, stores the result in a variable, and later fails with NotSerializableException when the pipeline resumes. What is the root cause?

Jenkins CPS pipelines checkpoint their state by serializing every live variable, and the map JsonSlurper hands back is a LazyMap, which is not Serializable. The parse works fine; the crash comes later when Jenkins tries to persist it. Standard fixes: do the parsing and extraction inside a @NonCPS method and return only plain strings or numbers, copy the result into a regular HashMap before storing it, or use the Pipeline Utility Steps readJSON step, which exists precisely for this. The lesson an interviewer wants to hear: know what type your parser actually returns, not just what it behaves like.

Q: Code doing long id = json.id passes all tests but intermittently throws ClassCastException in production. The JSON schema never changed. What is going on?

JsonSlurper picks the smallest comfortable type per value: small whole numbers arrive as Integer, values past the int range arrive as Long, and decimals arrive as BigDecimal. Test fixtures with ids like 42 produce Integer; production ids in the billions produce Long, and code written against one type meets the other. The robust pattern is to coerce explicitly, json.id as long or ((Number) json.id).longValue(), and never pattern-match on the concrete boxed type. The same trap hits currency fields: 19.99 is BigDecimal, not Double, which is actually what you want for money.

Q: A service parses a JSON payload once at startup and shares the resulting map with a pool of worker threads. Occasionally workers see odd behavior. Why is sharing this particular map risky?

The lazy map materializes values on first access, which means a read can be a write internally, and that internal mutation is not synchronized. Two threads touching the same untouched key at the same moment is a race, even though both callers believe they are only reading. The fix is cheap: after parsing, copy into an ordinary structure, new HashMap(parsed) for the top level or a deep copy if nested access is hot, and share that. Immutable snapshots of parsed config are also easier to reason about in general, independent of JsonSlurper.

Q: A batch job slurps a 500 MB JSON export and the JVM dies with OutOfMemoryError. The teammate’s proposal is simply more heap. What would you try first?

More heap postpones the problem; the real issue is that JsonSlurper builds the entire object graph in memory, and lazy maps carry bookkeeping overhead on top of the data. First, parse from a stream, slurper.parse(inputStream), instead of reading the file into one giant String and calling parseText, which briefly holds two copies. Second, switch the parser type: setType(JsonParserType.CHARACTER_SOURCE) is the variant intended for large payloads. If the job only needs a slice of each record, the honest answer is a streaming parser like Jackson’s, since slurping is a whole-document tool.

Q: A developer reports that parsed.class returns null instead of the object’s class, and a field called id works fine. Explain what Groovy is doing.

The parsed JSON object is a Map, and Groovy routes property access on maps through get(). So parsed.class is not a reflection call, it is a lookup of the key “class”, and since the payload has no such key, you get null. To ask for the runtime type you must call parsed.getClass() explicitly. The same shadowing bites keys named like Map methods or properties, so for JSON with unpredictable or reserved-word keys, bracket notation, parsed['class'], states your intent unambiguously. This one is a favorite in screens because it tests whether you know Groovy’s property dispatch rules, not just JSON.

Previous in Series: Groovy Builder Pattern Annotation

Next in Series: Groovy JSON Output with JsonBuilder

Related Topics You Might Like:

This post is part of the Groovy & Grails Cookbook series on TechnoScripts.com

RahulAuthor posts

Avatar for Rahul

Rahul is a passionate IT professional who loves to sharing his knowledge with others and inspiring them to expand their technical knowledge. Rahul's current objective is to write informative and easy-to-understand articles to help people avoid day-to-day technical issues altogether. Follow Rahul's blog to stay informed on the latest trends in IT and gain insights into how to tackle complex technical issues. Whether you're a beginner or an expert in the field, Rahul's articles are sure to leave you feeling inspired and informed.

No comment

Leave a Reply

Your email address will not be published. Required fields are marked *