Groovy XmlParser vs XmlSlurper – Complete Comparison with 12 Examples

Compare Groovy XmlParser and XmlSlurper with 12 side-by-side examples. Learn when to use each, performance differences, and migration tips. Groovy 5.0.6.

“There are two hard things in computer science: cache invalidation, naming things, and off-by-one errors.”

Programmer folklore, after Phil Karlton

Last Updated: July 2026 | Tested on: Groovy 5.0.6, 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.

The diagram below shows how the same XML string flows through each parser: XmlParser eagerly builds a full Node tree in memory that you can edit and serialize, while XmlSlurper defers the work and returns a lazy GPathResult that it evaluates only when you read a value:

Same XML stringXmlParser.parseText()XmlSlurper.parseText()Eager: build full tree nowgroovy.util.Node treein memoryAccess returns Nodeand NodeList, attrs areStringEdit in place, thenXmlUtil.serializeLazy evaluation:GPath resolved on accessGPathResult chainEvaluate on access:NodeChildren, Attributestext() gives String(read via GPath)Groovy XmlParser vs XmlSlurper: Parse Pipeline and Evaluation Timing

What Is Groovy 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:

FeatureXmlParserXmlSlurper
Return typegroovy.util.NodeGPathResult
EvaluationEager (immediate)Lazy (on access)
Memory modelFull tree in memoryLazy path-based
XML modificationYes (in-place)Limited (replaceNode/replaceBody)
Attribute accessnode.@attr or node.attribute('attr')node.@attr
Text accessnode.text()node.text()
Children iterationNode is iterableGPathResult is iterable
Best forRead + modify + writeRead-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

import groovy.xml.XmlParser
import groovy.xml.XmlSlurper

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.NodeChildren
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(). Note that rootS.item comes back as NodeChildren, a lazy collection of matches, even though only one element matches here. 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):
// append it first so the parent link is set, then move it to the front
def newSong = playlist.appendNode('song', [position: '0'], 'Imagine')
playlist.children().remove(newSong)
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. One catch we hit while testing: if you build a Node by hand with a null parent and add it straight to the children list, the parent link stays null and parent() throws a NullPointerException. Going through appendNode() first and then moving the node keeps the parent reference intact. 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(). The formatting is not perfectly clean either: after in-memory edits, serialize() leaves a few whitespace-only lines behind, and XmlNodePrinter prints each text value on its own line by default. Set preserveWhitespace = true like we did in Example 6 if you want the text inline.

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

import groovy.xml.*

// 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).intdiv(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).intdiv(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 ~120ms
XmlSlurper: 500 items parsed in ~63ms

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

ScenarioUse ThisWhy
Read values from XML responseXmlSlurperSimpler API, lazy evaluation
Parse config file, change values, saveXmlParserIn-place modification
Transform XML to JSON/MapEitherBoth work equally well
Add/remove XML nodesXmlParserNative appendNode/remove
Large file, read-onlyXmlSlurperLower memory footprint
Pipeline/streaming queriesXmlSlurperGPath is more natural
DOM-style tree walkingXmlParserReal 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 Node and GPathResult APIs – they look similar but are different types
  • Assume XmlParser’s node.title returns text – it returns a NodeList

Common Pitfalls

Pitfall 1: XmlParser Returns NodeList, Not Text

NodeList Pitfall

import groovy.xml.*

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[attributes={}; value=[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[attributes={}; value=[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

import groovy.xml.*

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

import groovy.xml.*

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, Groovy XmlParser is the right tool. 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

  • Groovy XmlParser returns Node objects; XmlSlurper returns GPathResult objects
  • 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, the groovy xmlparser class is the one to reach for 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.

Interview Questions on Groovy XmlParser vs XmlSlurper

“Scenario questions, not trivia: this is the form this topic takes in a real interview.”

Q: A log line built with "deploying version ${root.version}" prints deploying version [version[attributes={}; value=[2.0]]] after someone switched from XmlSlurper to XmlParser. What broke?

With XmlParser, root.version is a NodeList, and interpolating it prints the node’s internal representation, brackets and all. XmlSlurper’s GPathResult happens to render as its text, so the old code looked fine by accident. The portable habit is to be explicit at the boundary: root.version.text() gives the string under both parsers. This question checks whether you know the two APIs return different types even though the navigation syntax looks identical.

Q: A Gradle task rewrites a deployment descriptor with XmlParser and XmlUtil.serialize. It works, but the team complains that every run produces a huge diff and their XML comments vanish. Why?

XmlParser builds a tree of elements, attributes, and text only; comments are not represented as nodes, so a parse-and-serialize round trip drops them, and the serializer reformats whitespace to its own style. For machine-consumed XML that is fine. For hand-maintained files where reviewers read diffs, it is a real cost, and the honest answer is either targeted string or regex edits for tiny changes, or a comment-preserving DOM approach. Knowing that “parse, tweak, serialize” is lossy separates people who have actually shipped this from people who tried it once.

Q: A type check like if (node.@priority instanceof String) passes under XmlParser but fails after a refactor to XmlSlurper, even though the attribute is present. Explain.

The two parsers hand back different attribute types. XmlParser gives you the attribute value as a plain String. XmlSlurper wraps it in a GPathResult node, so instanceof String is false and anything that needs a real string, a map key, a JSON field, a Java API call, can misbehave downstream. Comparison with == still works because GPathResult compares by text, which hides the problem until serialization. The fix is the same discipline as with elements: call toString() or text() at the point where the value leaves XML-land.

Q: You add @CompileStatic to a Groovy utility class for speed, and every GPath navigation line like root.order.item stops compiling. What is the conflict, and what are your options?

GPath navigation is dynamic dispatch: order is not a real property of GPathResult or Node, it is resolved at runtime through getProperty. Static compilation demands compile-time-resolvable members, so the type checker rejects it. Options: leave the XML-touching methods dynamic and annotate them @CompileDynamic while the rest of the class stays static; use the explicit API (children(), getProperty(), or XPath-style helpers); or isolate parsing behind a small dynamic facade returning typed objects. Explaining why the magic works, and therefore why it dies under static typing, is the senior part of the answer.

Q: A nightly job must process a 700 MB XML export and the team is debating XmlParser versus XmlSlurper. What do you tell them?

That the debate is framed wrong. Both parsers materialize a document representation in heap; XmlSlurper is leaner and lazier, but at 700 MB you are still gambling with OutOfMemoryError either way. The right tool is streaming: a StAX pull parser or a SAX handler that processes elements as events and never holds the whole tree, with Groovy code wrapping it for readability. XmlSlurper versus XmlParser is a choice about API convenience and modification needs at document sizes that fit comfortably in memory; it is not a big-data strategy.

Previous in Series: Groovy XmlSlurper – Parse XML the Easy Way

Next in Series: Groovy Create and Modify XML Documents

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 *