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:
Tip: click the diagram to open it full screen, then use the zoom and pan controls for a closer look.
Table of Contents
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.jsonpackage – no external dependencies - Parses JSON objects into
Mapinstances and JSON arrays intoListinstances - Supports multiple parser types: CHAR_BUFFER (default), INDEX_OVERLAY, LAX, and CHARACTER_SOURCE
- Handles JSON numbers as
Integer,Long, orBigDecimaldepending on size - JSON booleans become Groovy
Booleanvalues andnullbecomes Groovynull - 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:
| Method | Input Source | Use Case |
|---|---|---|
parseText(String) | JSON string | Inline JSON, API responses stored as strings |
parse(Reader) | Reader | Files, streams |
parse(InputStream) | Input stream | HTTP connections, resources |
parse(File) | File object | Local JSON files |
parse(URL) | URL object | Remote JSON endpoints |
parse(byte[]) | Byte array | Binary 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
JsonSlurperinstances – they are thread-safe - Use
?.(safe navigation) and?:(Elvis operator) for optional fields - Wrap external JSON parsing in try-catch blocks
- Use
LAXparser 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 becomeLongorBigDecimal - Share lazy-parsed results across threads without converting first
- Catch
ExceptionwhenJsonExceptionis 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
JsonSlurperis 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, orBigDecimal– never assumeint - Use
LAXparser 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.
Related Posts
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

No comment