Compare Groovy XmlParser and XmlSlurper with 12 side-by-side examples. Learn when to use each, performance differences, and migration tips. Groovy 5.x.
“There are only two hard things in Computer Science: cache invalidation, naming things, and choosing the right XML parser.”
Every Java Developer, Eventually
Last Updated: March 2026 | Tested on: Groovy 5.x, Java 17+ | Difficulty: Intermediate | Reading Time: 20 minutes
Groovy gives you two built-in XML parsers: Groovy XmlParser and XmlSlurper. They look similar at first glance – both let you navigate XML with dot notation, both support GPath, and both can parse strings, files, and streams. So which one should you actually use?
The answer depends on what you’re trying to do. If you need to read XML data without changing it, XmlSlurper is typically the better choice. If you need to modify the XML tree and write it back, XmlParser is the way to go. But the differences go deeper than that – return types, memory models, evaluation strategies, and API quirks all factor in.
This XmlParser vs XmlSlurper comparison covers 12 practical side-by-side examples showing how both parsers handle the same tasks. If you haven’t read the XmlSlurper guide yet, start with Groovy XmlSlurper – Parse XML the Easy Way. When you’re ready to build XML from scratch, check out Groovy Create and Modify XML.
Table of Contents
What Is XmlParser?
groovy.xml.XmlParser parses XML and returns a tree of groovy.util.Node objects. Unlike XmlSlurper’s lazy GPathResult, XmlParser eagerly builds the complete DOM-like tree in memory. Each node holds references to its parent, children, attributes, and text.
According to the official Groovy XML processing documentation, XmlParser is the preferred choice when you need to modify the parsed XML – adding nodes, removing elements, or changing attribute values before serializing back to XML.
Key Differences at a Glance:
| Feature | XmlParser | XmlSlurper |
|---|---|---|
| Return type | groovy.util.Node | GPathResult |
| Evaluation | Eager (immediate) | Lazy (on access) |
| Memory model | Full tree in memory | Lazy path-based |
| XML modification | Yes (in-place) | Limited (replaceNode/replaceBody) |
| Attribute access | node.@attr or node.attribute('attr') | node.@attr |
| Text access | node.text() | node.text() |
| Children iteration | Node is iterable | GPathResult is iterable |
| Best for | Read + modify + write | Read-only access |
Why Compare XmlParser and XmlSlurper?
Developers often pick one parser by habit and use it for everything. That works until you hit a case where the other parser would be dramatically simpler. Here are the real-world scenarios where the choice matters:
- Read-only processing – XmlSlurper wins. Less memory, lazy evaluation, cleaner GPath.
- Modify and rewrite XML – XmlParser wins. In-place node manipulation, then serialize with XmlUtil.
- Large files – XmlSlurper is generally more memory-efficient.
- DOM-style tree walking – XmlParser returns real Node objects with parent/children references.
- Streaming/pipeline processing – XmlSlurper with GPath is more natural.
Syntax and API Differences
Parsing Syntax
Parsing Syntax Comparison
def xmlText = '<root><item>Hello</item></root>'
// XmlParser
def parserResult = new XmlParser().parseText(xmlText)
println "XmlParser type: ${parserResult.getClass().name}"
// XmlSlurper
def slurperResult = new XmlSlurper().parseText(xmlText)
println "XmlSlurper type: ${slurperResult.getClass().name}"
Output
XmlParser type: groovy.util.Node XmlSlurper type: groovy.xml.slurpersupport.NodeChild
The parsing API is nearly identical – both use parseText(), parse(File), and parse(InputStream). The difference is in what they return: Node vs GPathResult.
12 Side-by-Side Examples
Example 1: Basic Element Access
What we’re doing: Accessing child elements with both parsers.
Example 1: Basic Element Access
import groovy.xml.XmlParser
import groovy.xml.XmlSlurper
def xmlText = '''
<book>
<title>Groovy in Action</title>
<author>Dierk Koenig</author>
<year>2015</year>
</book>
'''
// XmlParser approach
def bookP = new XmlParser().parseText(xmlText)
println "--- XmlParser ---"
println "Title: ${bookP.title.text()}"
println "Author: ${bookP.author.text()}"
println "Year: ${bookP.year.text()}"
// XmlSlurper approach
def bookS = new XmlSlurper().parseText(xmlText)
println "\n--- XmlSlurper ---"
println "Title: ${bookS.title}"
println "Author: ${bookS.author}"
println "Year: ${bookS.year}"
Output
--- XmlParser --- Title: Groovy in Action Author: Dierk Koenig Year: 2015 --- XmlSlurper --- Title: Groovy in Action Author: Dierk Koenig Year: 2015
Import Note: In Groovy 4.x and later, groovy.xml.XmlParser and groovy.xml.XmlSlurper are no longer auto-imported, so you need the import statements shown above. The remaining examples in this post omit the imports for brevity. In Groovy 3.x and earlier, these imports were automatic.
What happened here: Both parsers support dot notation. The key difference: with XmlParser you must call text() explicitly because bookP.title returns a NodeList (a list of matching child nodes). With XmlSlurper, printing the GPathResult implicitly calls text().
Example 2: Attribute Access
What we’re doing: Reading XML attributes with both parsers.
Example 2: Attribute Access
import groovy.xml.*
def xmlText = '''
<products>
<product id="101" category="electronics">Laptop</product>
<product id="102" category="books">Groovy Cookbook</product>
</products>
'''
// XmlParser
def prodsP = new XmlParser().parseText(xmlText)
println "--- XmlParser ---"
prodsP.product.each { p ->
println "ID: ${p.@id}, Category: ${p.@category}, Name: ${p.text()}"
}
// Also works with attribute() method
println "First product attr map: ${prodsP.product[0].attributes()}"
// XmlSlurper
def prodsS = new XmlSlurper().parseText(xmlText)
println "\n--- XmlSlurper ---"
prodsS.product.each { p ->
println "ID: ${p.@id}, Category: ${p.@category}, Name: ${p.text()}"
}
Output
--- XmlParser --- ID: 101, Category: electronics, Name: Laptop ID: 102, Category: books, Name: Groovy Cookbook First product attr map: [id:101, category:electronics] --- XmlSlurper --- ID: 101, Category: electronics, Name: Laptop ID: 102, Category: books, Name: Groovy Cookbook
What happened here: Both parsers use @attributeName syntax. XmlParser’s Node has an extra attributes() method that returns all attributes as a Map. XmlParser returns attribute values as plain Strings, while XmlSlurper returns them as GPathResult objects.
Example 3: Return Types and Type Checking
What we’re doing: Examining the actual types returned by each parser.
Example 3: Return Types
import groovy.xml.*
def xmlText = '<root><item id="1">Hello</item></root>'
// XmlParser types
def rootP = new XmlParser().parseText(xmlText)
println "--- XmlParser Types ---"
println "Root: ${rootP.getClass().name}"
println "item children: ${rootP.item.getClass().name}"
println "item[0]: ${rootP.item[0].getClass().name}"
println "item[0].@id: ${rootP.item[0].@id.getClass().name}"
println "item[0].text(): ${rootP.item[0].text().getClass().name}"
// XmlSlurper types
def rootS = new XmlSlurper().parseText(xmlText)
println "\n--- XmlSlurper Types ---"
println "Root: ${rootS.getClass().name}"
println "item: ${rootS.item.getClass().name}"
println "item.@id: ${rootS.item.@id.getClass().name}"
println "item.text(): ${rootS.item.text().getClass().name}"
Output
--- XmlParser Types --- Root: groovy.util.Node item children: groovy.util.NodeList item[0]: groovy.util.Node item[0].@id: java.lang.String item[0].text(): java.lang.String --- XmlSlurper Types --- Root: groovy.xml.slurpersupport.NodeChild item: groovy.xml.slurpersupport.NodeChild item.@id: groovy.xml.slurpersupport.Attributes item.text(): java.lang.String
What happened here: This is the fundamental difference. XmlParser returns Node and NodeList objects with String attributes. XmlSlurper returns GPathResult subtypes for everything except text(). This matters when passing values to Java methods or using them as Map keys.
Example 4: Iterating Multiple Elements
What we’re doing: Iterating over repeated child elements with collection methods.
Example 4: Iterating Elements
import groovy.xml.*
def xmlText = '''
<scores>
<score subject="Math">92</score>
<score subject="English">88</score>
<score subject="Science">95</score>
<score subject="History">78</score>
</scores>
'''
// XmlParser
def scoresP = new XmlParser().parseText(xmlText)
println "--- XmlParser ---"
def avgP = scoresP.score.collect { it.text().toInteger() }.average()
println "Average: ${avgP}"
def topP = scoresP.score.max { it.text().toInteger() }
println "Top: ${topP.@subject} = ${topP.text()}"
// XmlSlurper
def scoresS = new XmlSlurper().parseText(xmlText)
println "\n--- XmlSlurper ---"
def avgS = scoresS.score.collect { it.text().toInteger() }.average()
println "Average: ${avgS}"
def topS = scoresS.score.max { it.text().toInteger() }
println "Top: ${topS.@subject} = ${topS.text()}"
Output
--- XmlParser --- Average: 88.25 Top: Science = 95 --- XmlSlurper --- Average: 88.25 Top: Science = 95
What happened here: Both parsers support Groovy collection methods like collect(), max(), findAll(), and each(). The code looks nearly identical. For read-only collection operations, both parsers are equally capable.
Example 5: Deep Search with depthFirst
What we’re doing: Searching the entire XML tree for elements at any depth.
Example 5: depthFirst Search
import groovy.xml.*
def xmlText = '''
<company>
<dept name="Engineering">
<employee>Alice</employee>
<employee>Bob</employee>
</dept>
<dept name="Marketing">
<employee>Charlie</employee>
</dept>
</company>
'''
// XmlParser - depthFirst() or '**'
def companyP = new XmlParser().parseText(xmlText)
println "--- XmlParser ---"
def allEmpsP = companyP.'**'.findAll { it.name() == 'employee' }
println "All employees: ${allEmpsP.collect { it.text() }}"
// XmlSlurper - depthFirst() or '**'
def companyS = new XmlSlurper().parseText(xmlText)
println "\n--- XmlSlurper ---"
def allEmpsS = companyS.'**'.findAll { it.name() == 'employee' }
println "All employees: ${allEmpsS.collect { it.text() }}"
Output
--- XmlParser --- All employees: [Alice, Bob, Charlie] --- XmlSlurper --- All employees: [Alice, Bob, Charlie]
What happened here: Both parsers support the '**' operator for depth-first search. The syntax is identical. The difference is in the returned types – XmlParser gives you Node objects, XmlSlurper gives you GPathResult objects.
Example 6: Modifying XML with XmlParser (Exclusive Feature)
What we’re doing: Adding, removing, and changing nodes – something XmlParser handles natively.
Example 6: Modifying XML (XmlParser)
import groovy.xml.*
def xmlText = '''
<team>
<member role="developer">Alice</member>
<member role="tester">Bob</member>
</team>
'''
def team = new XmlParser().parseText(xmlText)
// Add a new member
team.appendNode('member', [role: 'designer'], 'Charlie')
// Change Bob's role
def bob = team.member.find { it.text() == 'Bob' }
bob.@role = 'lead-tester'
// Remove Alice
def alice = team.member.find { it.text() == 'Alice' }
team.remove(alice)
// Serialize back to XML
def writer = new StringWriter()
def printer = new groovy.xml.XmlNodePrinter(new PrintWriter(writer))
printer.preserveWhitespace = true
printer.print(team)
println writer.toString()
Output
<team> <member role="lead-tester">Bob</member> <member role="designer">Charlie</member> </team>
What happened here: This is where XmlParser truly shines. You can appendNode() to add children, modify attributes by assignment, and remove() nodes – all operating on the in-memory tree. Then serialize with XmlNodePrinter. XmlSlurper cannot do this natively.
Example 7: XmlSlurper replaceNode (Limited Modification)
What we’re doing: Showing how XmlSlurper handles modifications with replaceNode() and replaceBody().
Example 7: XmlSlurper replaceNode
import groovy.xml.*
def xmlText = '''
<config>
<setting name="timeout">30</setting>
<setting name="retries">3</setting>
</config>
'''
def config = new XmlSlurper().parseText(xmlText)
// Replace the body of the timeout setting
config.setting.find { it.@name == 'timeout' }.replaceBody('60')
// Serialize - requires groovy.xml.XmlUtil
def output = groovy.xml.XmlUtil.serialize(config)
println output
Output
<?xml version="1.0" encoding="UTF-8"?><config> <setting name="timeout">60</setting> <setting name="retries">3</setting> </config>
What happened here: XmlSlurper does support replaceBody() and replaceNode(), but the API is more limited than XmlParser’s. You can replace content, but adding new sibling nodes or removing specific children is more cumbersome. For simple value updates, XmlSlurper works fine.
Example 8: Node Children vs GPath Children
What we’re doing: Comparing how each parser exposes child elements.
Example 8: Children Access
import groovy.xml.*
def xmlText = '''
<menu>
<appetizer>Soup</appetizer>
<main>Steak</main>
<dessert>Cake</dessert>
</menu>
'''
// XmlParser - children() returns all children including text nodes
def menuP = new XmlParser().parseText(xmlText)
println "--- XmlParser ---"
println "Children count: ${menuP.children().size()}"
menuP.children().each { child ->
println " ${child.name()} -> ${child.text()}"
}
// XmlSlurper - children() returns element children only
def menuS = new XmlSlurper().parseText(xmlText)
println "\n--- XmlSlurper ---"
println "Children count: ${menuS.children().size()}"
menuS.children().each { child ->
println " ${child.name()} -> ${child.text()}"
}
Output
--- XmlParser --- Children count: 3 appetizer -> Soup main -> Steak dessert -> Cake --- XmlSlurper --- Children count: 3 appetizer -> Soup main -> Steak dessert -> Cake
What happened here: Both parsers provide a children() method. XmlParser’s Node.children() can include text nodes as plain Strings mixed with child Nodes when there’s mixed content. XmlSlurper’s GPathResult.children() returns only element children.
Example 9: Namespace Handling Differences
What we’re doing: Comparing namespace handling in both parsers.
Example 9: Namespace Handling
import groovy.xml.*
def xmlText = '''
<root xmlns:ns="http://example.com/ns">
<ns:item>First</ns:item>
<ns:item>Second</ns:item>
</root>
'''
// XmlParser with namespaceAware = true (default)
def rootP = new XmlParser().parseText(xmlText)
println "--- XmlParser ---"
rootP.children().each { node ->
println "Name: ${node.name()}"
println "Local: ${node.name().localPart}"
println "NS: ${node.name().namespaceURI}"
println "Text: ${node.text()}"
println()
}
// XmlSlurper with declareNamespace
def rootS = new XmlSlurper().parseText(xmlText)
.declareNamespace(ns: 'http://example.com/ns')
println "--- XmlSlurper ---"
rootS.'ns:item'.each { item ->
println "Name: ${item.name()}"
println "Text: ${item.text()}"
}
Output
--- XmlParser ---
Name: {http://example.com/ns}item
Local: item
NS: http://example.com/ns
Text: First
Name: {http://example.com/ns}item
Local: item
NS: http://example.com/ns
Text: Second
--- XmlSlurper ---
Name: item
Text: First
Name: item
Text: Second
What happened here: XmlParser returns qualified names as QName objects with localPart and namespaceURI. XmlSlurper uses declareNamespace() and quoted property access. XmlSlurper’s approach is typically cleaner for namespace-heavy XML.
Example 10: Converting to Map Structures
What we’re doing: Converting XML to Groovy Maps with both parsers.
Example 10: XML to Map
import groovy.xml.*
def xmlText = '''
<user>
<name>Alice</name>
<email>alice@example.com</email>
<age>30</age>
</user>
'''
// XmlParser to Map
def userP = new XmlParser().parseText(xmlText)
def mapP = userP.children().collectEntries { [(it.name()): it.text()] }
println "--- XmlParser ---"
println "Map: ${mapP}"
println "Name: ${mapP.name}"
// XmlSlurper to Map
def userS = new XmlSlurper().parseText(xmlText)
def mapS = userS.children().collectEntries { [(it.name()): it.text()] }
println "\n--- XmlSlurper ---"
println "Map: ${mapS}"
println "Name: ${mapS.name}"
Output
--- XmlParser --- Map: [name:Alice, email:alice@example.com, age:30] Name: Alice --- XmlSlurper --- Map: [name:Alice, email:alice@example.com, age:30] Name: Alice
What happened here: The collectEntries pattern works identically for both parsers. Note that name() and text() are available on both Node and GPathResult. Once you convert to a Map, the parser difference disappears.
Example 11: XmlParser Node Manipulation (insert, reorder)
What we’re doing: Advanced node manipulation that only XmlParser supports.
Example 11: Advanced Node Manipulation
import groovy.xml.*
def xmlText = '''
<playlist>
<song position="1">Bohemian Rhapsody</song>
<song position="2">Stairway to Heaven</song>
<song position="3">Hotel California</song>
</playlist>
'''
def playlist = new XmlParser().parseText(xmlText)
// Insert a song at position 1 (index 0)
def newSong = new groovy.util.Node(null, 'song', [position: '0'], 'Imagine')
playlist.children().add(0, newSong)
// Update positions
playlist.song.eachWithIndex { song, idx ->
song.@position = "${idx + 1}"
}
// Print the modified playlist
playlist.song.each { song ->
println "#${song.@position}: ${song.text()}"
}
// Get parent info
def firstSong = playlist.song[0]
println "\nParent of '${firstSong.text()}': ${firstSong.parent().name()}"
Output
#1: Imagine #2: Bohemian Rhapsody #3: Stairway to Heaven #4: Hotel California Parent of 'Imagine': playlist
What happened here: XmlParser’s Node supports children().add(index, node) for insertion at specific positions, parent() for upward navigation, and direct attribute assignment. This kind of tree manipulation is XmlParser’s biggest advantage over XmlSlurper.
Example 12: Serializing Back to XML
What we’re doing: Converting modified XML trees back to string format.
Example 12: XML Serialization
import groovy.xml.*
def xmlText = '<root><item>Original</item></root>'
// XmlParser serialization methods
def rootP = new XmlParser().parseText(xmlText)
rootP.appendNode('item', 'Added')
// Method 1: XmlUtil.serialize
println "--- XmlUtil.serialize (XmlParser) ---"
println groovy.xml.XmlUtil.serialize(rootP)
// Method 2: XmlNodePrinter (more control)
println "--- XmlNodePrinter ---"
def sw = new StringWriter()
new groovy.xml.XmlNodePrinter(new PrintWriter(sw)).print(rootP)
println sw.toString()
// XmlSlurper serialization
def rootS = new XmlSlurper().parseText(xmlText)
println "--- XmlUtil.serialize (XmlSlurper) ---"
println groovy.xml.XmlUtil.serialize(rootS)
Output
--- XmlUtil.serialize (XmlParser) --- <?xml version="1.0" encoding="UTF-8"?><root> <item>Original</item> <item>Added</item> </root> --- XmlNodePrinter --- <root> <item>Original</item> <item>Added</item> </root> --- XmlUtil.serialize (XmlSlurper) --- <?xml version="1.0" encoding="UTF-8"?><root> <item>Original</item> </root>
What happened here: groovy.xml.XmlUtil.serialize() works with both parsers. XmlNodePrinter gives more formatting control but only works with XmlParser’s Node objects. Notice that XmlSlurper’s output doesn’t include the “Added” item – because XmlSlurper doesn’t support appendNode().
Performance Comparison
For most real-world XML documents (under a few MB), the performance difference is negligible. Here’s a quick benchmark overview:
Performance Comparison
// Generate a moderately sized XML string
def xmlBuilder = new StringBuilder('<data>')
500.times { i ->
xmlBuilder.append("<item id=\"${i}\">Value ${i}</item>")
}
xmlBuilder.append('</data>')
def bigXml = xmlBuilder.toString()
// Benchmark XmlParser
def startP = System.nanoTime()
def resultP = new XmlParser().parseText(bigXml)
def countP = resultP.item.size()
def timeP = (System.nanoTime() - startP) / 1_000_000
// Benchmark XmlSlurper
def startS = System.nanoTime()
def resultS = new XmlSlurper().parseText(bigXml)
def countS = resultS.item.size()
def timeS = (System.nanoTime() - startS) / 1_000_000
println "XmlParser: ${countP} items parsed in ~${timeP}ms"
println "XmlSlurper: ${countS} items parsed in ~${timeS}ms"
println "\nNote: First-run times include JVM warmup."
println "In practice, both are fast enough for typical XML documents."
Output
XmlParser: 500 items parsed in ~85ms XmlSlurper: 500 items parsed in ~42ms Note: First-run times include JVM warmup. In practice, both are fast enough for typical XML documents.
XmlSlurper tends to be faster for parsing because of its lazy evaluation – it doesn’t build the full Node tree eagerly. But XmlParser can be faster for repeated access to the same nodes because the tree is already materialized. Choose based on your use case, not benchmarks.
Edge Cases and Best Practices
Decision Guide: When to Use Which
| Scenario | Use This | Why |
|---|---|---|
| Read values from XML response | XmlSlurper | Simpler API, lazy evaluation |
| Parse config file, change values, save | XmlParser | In-place modification |
| Transform XML to JSON/Map | Either | Both work equally well |
| Add/remove XML nodes | XmlParser | Native appendNode/remove |
| Large file, read-only | XmlSlurper | Lower memory footprint |
| Pipeline/streaming queries | XmlSlurper | GPath is more natural |
| DOM-style tree walking | XmlParser | Real Node objects with parent |
Best Practices Summary
DO:
- Default to XmlSlurper for read-only XML processing
- Use XmlParser when you need to modify and serialize XML
- Call
text()to get plain Strings from both parsers - Use
XmlUtil.serialize()for quick XML output from either parser - Disable external entities when parsing untrusted XML
DON’T:
- Use XmlSlurper if you need to add or remove nodes from the tree
- Mix up
NodeandGPathResultAPIs – they look similar but are different types - Assume XmlParser’s
node.titlereturns text – it returns aNodeList
Common Pitfalls
Pitfall 1: XmlParser Returns NodeList, Not Text
NodeList Pitfall
def xmlText = '<root><name>Alice</name></root>'
def rootP = new XmlParser().parseText(xmlText)
// This prints the NodeList, not the text!
println "Direct: ${rootP.name}" // [name[Alice]]
println "With text(): ${rootP.name.text()}" // Alice
println "First item: ${rootP.name[0].text()}" // Alice
// XmlSlurper is more forgiving
def rootS = new XmlSlurper().parseText(xmlText)
println "Slurper: ${rootS.name}" // Alice (implicit text())
Output
Direct: [name[Alice]] With text(): Alice First item: Alice Slurper: Alice
With XmlParser, rootP.name returns a NodeList. You must call text() to get the string content. XmlSlurper’s GPathResult handles this more gracefully by converting to text when coerced to String.
Pitfall 2: Attribute Types Differ Between Parsers
Attribute Type Pitfall
def xmlText = '<item id="42" />'
// XmlParser: attributes are Strings
def itemP = new XmlParser().parseText(xmlText)
println "Parser @id type: ${itemP.@id.getClass().name}"
println "Parser @id == '42': ${itemP.@id == '42'}"
// XmlSlurper: attributes are GPathResult
def itemS = new XmlSlurper().parseText(xmlText)
println "Slurper @id type: ${itemS.@id.getClass().name}"
println "Slurper @id == '42': ${itemS.@id == '42'}"
// Safe: always use toString() for type-sensitive operations
def map = [(itemS.@id.toString()): 'found']
println "Map lookup: ${map['42']}"
Output
Parser @id type: java.lang.String Parser @id == '42': true Slurper @id type: groovy.xml.slurpersupport.Attributes Slurper @id == '42': true Map lookup: found
XmlParser gives you String attributes directly, while XmlSlurper wraps them in Attributes (a GPathResult subtype). Equality comparisons work in both cases because of Groovy coercion, but type checks and Map keys require explicit conversion.
Pitfall 3: Confusing the Two Parser APIs
API Confusion
def xmlText = '<root><item>Test</item></root>'
// XmlParser has value(), XmlSlurper doesn't
def rootP = new XmlParser().parseText(xmlText)
println "Parser value(): ${rootP.item[0].value()}"
// XmlSlurper has isEmpty(), XmlParser's Node doesn't have it the same way
def rootS = new XmlSlurper().parseText(xmlText)
println "Slurper isEmpty: ${rootS.nonexistent.isEmpty()}"
// XmlParser: check for missing elements differently
def missing = rootP.nonexistent
println "Parser missing: ${missing}"
println "Parser missing empty: ${missing.isEmpty()}" // empty NodeList
Output
Parser value(): [Test] Slurper isEmpty: true Parser missing: [] Parser missing empty: true
Some methods exist in one API but not the other. XmlParser’s Node has value() (returns children list), attributes(), and parent(). XmlSlurper’s GPathResult has isEmpty(), replaceNode(), and replaceBody(). Don’t assume the APIs are interchangeable.
Conclusion
Both Groovy XmlParser and XmlSlurper are excellent tools, but they serve different purposes. XmlSlurper is your go-to for reading XML – it’s lazy, memory-efficient, and the GPath integration feels natural. XmlParser is the right choice when you need to modify XML – adding, removing, and rearranging nodes before writing the result back.
The simple rule: if you’re only reading, use XmlSlurper. If you’re reading and writing, use XmlParser. For everything else, both work fine.
For more details into XmlSlurper, see Groovy XmlSlurper – Parse XML the Easy Way. When you’re ready to create XML from scratch using MarkupBuilder and StreamingMarkupBuilder, head over to Groovy Create and Modify XML Documents.
Summary
- XmlParser returns
Nodeobjects; XmlSlurper returnsGPathResultobjects - XmlParser is eager (full tree); XmlSlurper is lazy (on-demand)
- Only XmlParser supports in-place XML modification (appendNode, remove)
- Both support dot notation,
@attributes, and the'**'deep search - Use
XmlUtil.serialize()to convert either parser’s output back to XML strings
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 Create and Modify XML Documents
Frequently Asked Questions
Should I use XmlParser or XmlSlurper for read-only XML parsing?
Use XmlSlurper for read-only parsing. It uses lazy evaluation and is more memory-efficient. XmlSlurper‘s GPathResult also provides a cleaner API for navigating and querying XML without modification.
Can XmlSlurper modify XML documents?
XmlSlurper has limited modification support through replaceNode() and replaceBody() methods, but it cannot add new nodes, remove existing ones, or reorder children. For full XML modification capabilities, use XmlParser instead.
What does XmlParser return compared to XmlSlurper?
XmlParser returns groovy.util.Node objects that form a DOM-like tree with parent references. XmlSlurper returns groovy.xml.slurpersupport.GPathResult objects that use lazy evaluation. Both support dot notation and GPath, but the underlying types and APIs differ.
Which is faster, XmlParser or XmlSlurper?
XmlSlurper is generally faster for initial parsing due to lazy evaluation. However, XmlParser can be faster for repeated access to the same nodes since the tree is already built in memory. For most practical use cases, the performance difference is negligible.
How do I serialize XML back to a string after modification?
Use groovy.xml.XmlUtil.serialize() – it works with both XmlParser‘s Node and XmlSlurper‘s GPathResult. For more control over formatting with XmlParser, use XmlNodePrinter with a PrintWriter and StringWriter.
Related Posts
Previous in Series: Groovy XmlSlurper – Parse XML the Easy Way
Next in Series: Groovy Create and Modify XML Documents
Related Topics You Might Like:
- Groovy XmlSlurper – Parse XML the Easy Way
- Groovy Create and Modify XML Documents
- Groovy findAll Method – Filter Collections Like a Pro
This post is part of the Groovy & Grails Cookbook series on TechnoScripts.com

No comment