Learn to Groovy create XML and modify with 12 examples using MarkupBuilder, StreamingMarkupBuilder, XmlParser, and XmlUtil. Tested on Groovy 5.x.
“Writing XML by hand is like writing assembly – you can do it, but there are better tools for the job.”
Tim Bray, XML Specification Co-author
Last Updated: March 2026 | Tested on: Groovy 5.x, Java 17+ | Difficulty: Intermediate | Reading Time: 22 minutes
Reading XML is only half the story – in real projects, you also need to groovy create xml from scratch and modify existing documents for API requests, configuration files, and data exports. If you’ve already seen how to parse XML with XmlSlurper and understand the differences between XmlParser and XmlSlurper, this post covers the other direction: building and changing XML with Groovy’s builder pattern.
Groovy makes creating XML surprisingly elegant with its builder pattern. Instead of concatenating angle brackets and hoping you don’t miss a closing tag, you write code that looks like a blueprint of your XML structure. Groovy modify XML operations are equally clean when you combine XmlParser with the serialization utilities.
This post covers 12 practical examples using MarkupBuilder, StreamingMarkupBuilder, XmlParser for modification, and XmlUtil for serialization. Every example includes tested output.
Table of Contents
What Are Groovy’s XML Builders?
Groovy provides two main classes for creating XML documents:
groovy.xml.MarkupBuilder– Writes XML directly to a Writer as you build it. Simple, synchronous, and great for most use cases.groovy.xml.StreamingMarkupBuilder– Creates a closure-based template that can be serialized later. Supports namespaces, processing instructions, and deferred output.
For modifying existing XML, you parse with XmlParser, manipulate the Node tree, and serialize back with XmlUtil or XmlNodePrinter.
According to the official Groovy XML processing documentation, both builders use Groovy’s metaprogramming to turn method calls into XML elements – a pattern called “builder syntax.”
Key Points:
- MarkupBuilder writes XML immediately to a Writer
- StreamingMarkupBuilder creates a deferred markup template
- Method names become element names, named parameters become attributes
- Closures define nested child elements
- XmlParser + XmlUtil handles parse-modify-serialize workflows
- Both builders produce well-formed XML automatically
Why Use Builders Instead of String Concatenation?
You could always build XML with string concatenation or GString interpolation. But that approach has serious problems:
| Approach | Well-formed? | Escaping? | Readability | Maintenance |
|---|---|---|---|---|
| String concatenation | No guarantee | Manual | Poor | Fragile |
| GString templates | No guarantee | Manual | OK | Fragile |
| MarkupBuilder | Always valid | Automatic | Excellent | Easy |
| StreamingMarkupBuilder | Always valid | Automatic | Good | Easy |
Builders handle XML escaping automatically – if your data contains <, >, &, or quotes, the builder escapes them correctly. String concatenation doesn’t, and that leads to broken XML or security issues.
Syntax and Builder Overview
MarkupBuilder Syntax
MarkupBuilder Syntax
def writer = new StringWriter()
def xml = new groovy.xml.MarkupBuilder(writer)
// Method name = element name
// Map parameter = attributes
// String parameter = text content
// Closure = child elements
xml.root {
element(attribute: 'value', 'Text content')
parent {
child('Nested text')
}
}
println writer.toString()
Output
<root>
<element attribute='value'>Text content</element>
<parent>
<child>Nested text</child>
</parent>
</root>
StreamingMarkupBuilder Syntax
StreamingMarkupBuilder Syntax
def builder = new groovy.xml.StreamingMarkupBuilder()
builder.encoding = 'UTF-8'
def markup = builder.bind {
mkp.xmlDeclaration()
root {
item(id: '1', 'Hello')
}
}
println groovy.xml.XmlUtil.serialize(markup)
Output
<?xml version="1.0" encoding="UTF-8"?><root> <item id="1">Hello</item> </root>
The core concept is the same: method calls create elements, maps create attributes, strings create text content, and closures create children. StreamingMarkupBuilder uses bind() to create a template and mkp for special instructions like XML declarations.
12 Practical Examples
Example 1: Create Simple XML with MarkupBuilder
What we’re doing: Building a simple XML document with elements, attributes, and text.
Example 1: Simple MarkupBuilder
import groovy.xml.MarkupBuilder
import groovy.xml.StreamingMarkupBuilder
import groovy.xml.XmlParser
import groovy.xml.XmlSlurper
import groovy.xml.XmlNodePrinter
def writer = new StringWriter()
def xml = new MarkupBuilder(writer)
xml.person {
name('Alice Johnson')
age(30)
email(type: 'work', 'alice@company.com')
email(type: 'personal', 'alice@gmail.com')
address {
street('123 Main St')
city('New York')
state('NY')
zip('10001')
}
}
println writer.toString()
Output
<person>
<name>Alice Johnson</name>
<age>30</age>
<email type='work'>alice@company.com</email>
<email type='personal'>alice@gmail.com</email>
<address>
<street>123 Main St</street>
<city>New York</city>
<state>NY</state>
<zip>10001</zip>
</address>
</person>
Import Note: In Groovy 4.x and later, groovy.xml.MarkupBuilder, groovy.xml.StreamingMarkupBuilder, groovy.xml.XmlParser, groovy.xml.XmlSlurper, and groovy.xml.XmlNodePrinter are no longer auto-imported. The import statements shown above cover all XML classes used throughout this post. The remaining examples omit the imports for brevity, but some use the fully-qualified class name (e.g., new groovy.xml.MarkupBuilder(writer)) which also works without an import. In Groovy 3.x and earlier, these imports were automatic.
What happened here: Each method call on the builder creates an XML element. The method name becomes the tag name. A string argument becomes the text content. A map argument becomes attributes. A closure creates nested children. MarkupBuilder handles indentation and closing tags automatically.
Example 2: Create XML from Collections
What we’re doing: Generating XML dynamically from a list of maps.
Example 2: XML from Collections
def employees = [
[id: 'E001', name: 'Alice', dept: 'Engineering', salary: 95000],
[id: 'E002', name: 'Bob', dept: 'Marketing', salary: 72000],
[id: 'E003', name: 'Charlie', dept: 'Engineering', salary: 88000],
]
def writer = new StringWriter()
def xml = new groovy.xml.MarkupBuilder(writer)
xml.employees {
employees.each { emp ->
employee(id: emp.id) {
name(emp.name)
department(emp.dept)
salary(emp.salary)
}
}
}
println writer.toString()
Output
<employees>
<employee id='E001'>
<name>Alice</name>
<department>Engineering</department>
<salary>95000</salary>
</employee>
<employee id='E002'>
<name>Bob</name>
<department>Marketing</department>
<salary>72000</salary>
</employee>
<employee id='E003'>
<name>Charlie</name>
<department>Engineering</department>
<salary>88000</salary>
</employee>
</employees>
What happened here: You can use any Groovy code inside builder closures – loops, conditionals, method calls. Here, each() iterates over the list and creates an <employee> element for each map entry. This is exactly how you’d generate XML from database results or API data.
Example 3: MarkupBuilder with Special Characters
What we’re doing: Showing that MarkupBuilder automatically escapes special XML characters.
Example 3: Special Characters
def writer = new StringWriter()
def xml = new groovy.xml.MarkupBuilder(writer)
xml.data {
// Special characters in text
formula('x < 10 && y > 5')
company('AT&T')
quote('She said "hello"')
// Special characters in attributes
query(sql: "SELECT * WHERE id > 0 AND name = 'test'", 'results')
}
println writer.toString()
Output
<data> <formula>x < 10 && y > 5</formula> <company>AT&T</company> <quote>She said "hello"</quote> <query sql='SELECT * WHERE id > 0 AND name = 'test''>results</query> </data>
What happened here: MarkupBuilder automatically escapes <, >, &, ", and ' in both element text and attribute values. This is a major advantage over string concatenation – you never have to worry about broken XML from user data.
Example 4: StreamingMarkupBuilder with Namespaces
What we’re doing: Creating XML with namespace declarations using StreamingMarkupBuilder.
Example 4: Namespaces
def builder = new groovy.xml.StreamingMarkupBuilder()
builder.encoding = 'UTF-8'
def markup = builder.bind {
mkp.xmlDeclaration()
namespaces << [app: 'http://example.com/app']
namespaces << [db: 'http://example.com/db']
'app:config' {
'app:setting'(name: 'timeout', '30')
'app:setting'(name: 'retries', '3')
'db:connection' {
'db:host'('localhost')
'db:port'('5432')
}
}
}
println groovy.xml.XmlUtil.serialize(markup)
Output
<?xml version="1.0" encoding="UTF-8"?><app:config xmlns:app="http://example.com/app" xmlns:db="http://example.com/db">
<app:setting name="timeout">30</app:setting>
<app:setting name="retries">3</app:setting>
<db:connection>
<db:host>localhost</db:host>
<db:port>5432</db:port>
</db:connection>
</app:config>
What happened here: StreamingMarkupBuilder supports namespaces through the namespaces property. Use << to register prefixes, then reference them in quoted method names like 'app:config'. The builder places namespace declarations on the root element automatically.
Example 5: StreamingMarkupBuilder with CDATA and Comments
What we’re doing: Adding CDATA sections, comments, and processing instructions.
Example 5: CDATA and Comments
def builder = new groovy.xml.StreamingMarkupBuilder()
def markup = builder.bind {
mkp.xmlDeclaration()
mkp.comment('Generated by Groovy on 2026-03-08')
mkp.pi('xml-stylesheet', 'type="text/xsl" href="transform.xsl"')
articles {
article(id: '1') {
title('Getting Started with Groovy')
body {
mkp.yieldUnescaped('<![CDATA[Use <code> tags for inline code. Special chars like < and > are safe here.]]>')
}
}
mkp.comment('More articles coming soon')
article(id: '2') {
title('Advanced XML Handling')
body {
mkp.yieldUnescaped('<![CDATA[Groovy makes XML <easy> to work with.]]>')
}
}
}
}
println groovy.xml.XmlUtil.serialize(markup)
Output
<?xml version="1.0" encoding="UTF-8"?><!--Generated by Groovy on 2026-03-08--><articles>
<article id="1">
<title>Getting Started with Groovy</title>
<body><![CDATA[Use tags for inline code. Special chars like < and > are safe here.]]></body>
</article>
<!--More articles coming soon-->
<article id="2">
<title>Advanced XML Handling</title>
<body><![CDATA[Groovy makes XML <easy> to work with.]]></body>
</article>
</articles>
What happened here: The mkp helper object provides special operations: comment() for XML comments, pi() for processing instructions, yieldUnescaped() for raw content like CDATA, and xmlDeclaration() for the XML prolog. These features are exclusive to StreamingMarkupBuilder.
Example 6: Modify Existing XML with XmlParser
What we’re doing: Parsing existing XML, modifying it, and serializing the result.
Example 6: Modify XML
import groovy.xml.*
def xmlText = '''
<config>
<database>
<host>localhost</host>
<port>3306</port>
<name>dev_db</name>
</database>
<cache enabled="false">
<ttl>300</ttl>
</cache>
</config>
'''
def config = new XmlParser().parseText(xmlText)
// 1. Change database host for production
config.database.host[0].value = ['prod-db.example.com']
// 2. Change database name
config.database.name[0].value = ['prod_db']
// 3. Enable cache
config.cache[0].@enabled = 'true'
// 4. Change cache TTL
config.cache.ttl[0].value = ['600']
// 5. Add a new element
config.appendNode('logging', [level: 'INFO'])
// Serialize
println groovy.xml.XmlUtil.serialize(config)
Output
<?xml version="1.0" encoding="UTF-8"?><config>
<database>
<host>prod-db.example.com</host>
<port>3306</port>
<name>prod_db</name>
</database>
<cache enabled="true">
<ttl>600</ttl>
</cache>
<logging level="INFO"/>
</config>
What happened here: This is the classic parse-modify-serialize pattern. You parse with XmlParser to get mutable Node objects, change values by assigning to node.value (a list of children), modify attributes with @attr = syntax, add new nodes with appendNode(), and serialize with XmlUtil.serialize().
Example 7: Add Complex Child Nodes
What we’re doing: Adding nested child structures to an existing XML document.
Example 7: Add Complex Children
import groovy.xml.*
def xmlText = '''
<catalog>
<product id="P001">
<name>Laptop</name>
<price>999.99</price>
</product>
</catalog>
'''
def catalog = new XmlParser().parseText(xmlText)
// Add a new product with nested children
def newProduct = catalog.appendNode('product', [id: 'P002'])
newProduct.appendNode('name', 'Wireless Mouse')
newProduct.appendNode('price', '29.99')
newProduct.appendNode('specs').with {
appendNode('weight', '85g')
appendNode('battery', 'AA')
appendNode('connectivity', 'Bluetooth 5.0')
}
// Add a review to the existing product
def laptop = catalog.product.find { it.@id == 'P001' }
laptop.appendNode('reviews').with {
appendNode('review', [stars: '5'], 'Excellent performance!')
appendNode('review', [stars: '4'], 'Great value for money.')
}
def writer = new StringWriter()
new groovy.xml.XmlNodePrinter(new PrintWriter(writer)).print(catalog)
println writer.toString()
Output
<catalog>
<product id="P001">
<name>Laptop</name>
<price>999.99</price>
<reviews>
<review stars="5">Excellent performance!</review>
<review stars="4">Great value for money.</review>
</reviews>
</product>
<product id="P002">
<name>Wireless Mouse</name>
<price>29.99</price>
<specs>
<weight>85g</weight>
<battery>AA</battery>
<connectivity>Bluetooth 5.0</connectivity>
</specs>
</product>
</catalog>
What happened here: The with() method chains nicely with appendNode() – you create a parent node, then use with to add children to it in a clean block. This approach is great for building complex nested structures within an existing document.
Example 8: Remove and Replace Nodes
What we’re doing: Removing specific nodes and replacing them with new content.
Example 8: Remove and Replace
import groovy.xml.*
def xmlText = '''
<users>
<user status="active">
<name>Alice</name>
<role>admin</role>
</user>
<user status="inactive">
<name>Bob</name>
<role>viewer</role>
</user>
<user status="active">
<name>Charlie</name>
<role>editor</role>
</user>
</users>
'''
def users = new XmlParser().parseText(xmlText)
// Remove all inactive users
def inactiveUsers = users.user.findAll { it.@status == 'inactive' }
inactiveUsers.each { users.remove(it) }
// Replace Charlie's role
def charlie = users.user.find { it.name.text() == 'Charlie' }
def oldRole = charlie.role[0]
charlie.remove(oldRole)
charlie.appendNode('role', 'admin')
// Add a timestamp attribute to remaining users
users.user.each { it.@lastModified = '2026-03-08' }
println groovy.xml.XmlUtil.serialize(users)
Output
<?xml version="1.0" encoding="UTF-8"?><users>
<user status="active" lastModified="2026-03-08">
<name>Alice</name>
<role>admin</role>
</user>
<user status="active" lastModified="2026-03-08">
<name>Charlie</name>
<role>admin</role>
</user>
</users>
What happened here: To remove a node, call parent.remove(child). To replace content, remove the old node and append a new one. Important: collect nodes to remove before iterating – modifying a collection while iterating over it can cause ConcurrentModificationException.
Example 9: MarkupBuilder with Conditional Content
What we’re doing: Using conditionals and loops inside MarkupBuilder closures.
Example 9: Conditional XML Generation
def orders = [
[id: 'ORD-001', customer: 'Alice', items: ['Laptop', 'Mouse'], priority: true],
[id: 'ORD-002', customer: 'Bob', items: ['Keyboard'], priority: false],
[id: 'ORD-003', customer: 'Charlie', items: ['Monitor', 'Cable', 'Stand'], priority: true],
]
def writer = new StringWriter()
def xml = new groovy.xml.MarkupBuilder(writer)
xml.orders(generated: '2026-03-08', count: orders.size()) {
orders.each { ord ->
// Conditionally add priority attribute
def attrs = [id: ord.id]
if (ord.priority) {
attrs.priority = 'high'
}
order(attrs) {
customer(ord.customer)
items {
ord.items.each { itemName ->
item(itemName)
}
}
// Conditionally add element
if (ord.items.size() > 2) {
note('Large order - requires special handling')
}
}
}
}
println writer.toString()
Output
<orders generated='2026-03-08' count='3'>
<order id='ORD-001' priority='high'>
<customer>Alice</customer>
<items>
<item>Laptop</item>
<item>Mouse</item>
</items>
</order>
<order id='ORD-002'>
<customer>Bob</customer>
<items>
<item>Keyboard</item>
</items>
</order>
<order id='ORD-003' priority='high'>
<customer>Charlie</customer>
<items>
<item>Monitor</item>
<item>Cable</item>
<item>Stand</item>
</items>
<note>Large order - requires special handling</note>
</order>
</orders>
What happened here: Inside a MarkupBuilder closure, you can use any Groovy construct – if statements, loops, method calls, variable assignments. The builder only creates XML elements for the builder method calls that actually execute. This gives you full programmatic control over the XML structure.
Example 10: Write XML to a File
What we’re doing: Creating XML and writing it directly to a file.
Example 10: Write to File
// Create a temp file
def tempFile = File.createTempFile('config', '.xml')
tempFile.deleteOnExit()
// MarkupBuilder writing directly to a file
new File(tempFile.absolutePath).withWriter('UTF-8') { writer ->
def xml = new groovy.xml.MarkupBuilder(writer)
xml.mkp.xmlDeclaration()
xml.configuration(version: '1.0') {
server {
host('0.0.0.0')
port(8080)
ssl(enabled: 'true') {
certFile('/etc/ssl/cert.pem')
keyFile('/etc/ssl/key.pem')
}
}
database {
url('jdbc:postgresql://localhost:5432/myapp')
pool(min: '5', max: '20')
}
}
}
// Read back and print
println "File written to: ${tempFile.name}"
println "File size: ${tempFile.length()} bytes"
println "\nContent:"
println tempFile.text
Output
File written to: config1234567890.xml
File size: 312 bytes
Content:
<?xml version='1.0' encoding='UTF-8'?>
<configuration version='1.0'>
<server>
<host>0.0.0.0</host>
<port>8080</port>
<ssl enabled='true'>
<certFile>/etc/ssl/cert.pem</certFile>
<keyFile>/etc/ssl/key.pem</keyFile>
</ssl>
</server>
<database>
<url>jdbc:postgresql://localhost:5432/myapp</url>
<pool min='5' max='20' />
</database>
</configuration>
What happened here: Instead of writing to a StringWriter, you pass a FileWriter to MarkupBuilder. Using withWriter() ensures the file is properly closed after writing. The mkp.xmlDeclaration() adds the XML prolog.
Example 11: Transform XML Structure
What we’re doing: Reading XML in one format and writing it in a different structure.
Example 11: XML Transformation
import groovy.xml.*
// Source XML - flat structure
def sourceXml = '''
<records>
<record>
<firstName>Alice</firstName>
<lastName>Johnson</lastName>
<dept>Engineering</dept>
<salary>95000</salary>
</record>
<record>
<firstName>Bob</firstName>
<lastName>Smith</lastName>
<dept>Engineering</dept>
<salary>88000</salary>
</record>
<record>
<firstName>Charlie</firstName>
<lastName>Brown</lastName>
<dept>Marketing</dept>
<salary>72000</salary>
</record>
</records>
'''
// Parse source
def records = new XmlSlurper().parseText(sourceXml)
// Transform to grouped structure using MarkupBuilder
def writer = new StringWriter()
def xml = new groovy.xml.MarkupBuilder(writer)
// Group records by department
def grouped = records.record.groupBy { it.dept.text() }
xml.organization {
grouped.each { deptName, members ->
department(name: deptName, headcount: members.size()) {
members.each { m ->
employee {
fullName("${m.firstName} ${m.lastName}")
salary(m.salary.text())
}
}
}
}
}
println writer.toString()
Output
<organization>
<department name='Engineering' headcount='2'>
<employee>
<fullName>Alice Johnson</fullName>
<salary>95000</salary>
</employee>
<employee>
<fullName>Bob Smith</fullName>
<salary>88000</salary>
</employee>
</department>
<department name='Marketing' headcount='1'>
<employee>
<fullName>Charlie Brown</fullName>
<salary>72000</salary>
</employee>
</department>
</organization>
What happened here: This is XML transformation in action – read one structure, reshape the data, write a different structure. We used XmlSlurper to parse (read-only), groupBy() to restructure the data, and MarkupBuilder to generate the new format. This pattern replaces XSLT for many use cases.
Example 12: Build a SOAP Request XML
What we’re doing: Creating a realistic SOAP request envelope with StreamingMarkupBuilder.
Example 12: SOAP Request
def builder = new groovy.xml.StreamingMarkupBuilder()
builder.encoding = 'UTF-8'
def soapRequest = builder.bind {
mkp.xmlDeclaration()
namespaces << [
'soap': 'http://schemas.xmlsoap.org/soap/envelope/',
'ws' : 'http://example.com/webservice'
]
'soap:Envelope' {
'soap:Header' {
'ws:Authentication' {
'ws:ApiKey'('sk-abc123def456')
'ws:Timestamp'('2026-03-08T10:30:00Z')
}
}
'soap:Body' {
'ws:GetUserRequest' {
'ws:UserId'('101')
'ws:IncludeDetails'('true')
'ws:Fields' {
'ws:Field'('name')
'ws:Field'('email')
'ws:Field'('role')
}
}
}
}
}
println groovy.xml.XmlUtil.serialize(soapRequest)
Output
<?xml version="1.0" encoding="UTF-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ws="http://example.com/webservice">
<soap:Header>
<ws:Authentication>
<ws:ApiKey>sk-abc123def456</ws:ApiKey>
<ws:Timestamp>2026-03-08T10:30:00Z</ws:Timestamp>
</ws:Authentication>
</soap:Header>
<soap:Body>
<ws:GetUserRequest>
<ws:UserId>101</ws:UserId>
<ws:IncludeDetails>true</ws:IncludeDetails>
<ws:Fields>
<ws:Field>name</ws:Field>
<ws:Field>email</ws:Field>
<ws:Field>role</ws:Field>
</ws:Fields>
</ws:GetUserRequest>
</soap:Body>
</soap:Envelope>
What happened here: StreamingMarkupBuilder is perfect for SOAP and other namespace-heavy XML. You register all namespace prefixes upfront, then use them naturally in element names. The builder produces valid, well-formed XML with all namespace declarations in the right place. Compare this to building SOAP XML with string concatenation – the builder version is dramatically cleaner and safer.
Edge Cases and Best Practices
Element Names with Hyphens or Special Characters
Special Element Names
def writer = new StringWriter()
def xml = new groovy.xml.MarkupBuilder(writer)
xml.root {
// Hyphenated element names need quotes
'first-name'('Alice')
'last-name'('Johnson')
'date-of-birth'('1996-05-15')
// Element names with dots
'app.config'(version: '2.0', 'active')
// Numeric-starting names (need quotes)
'_123data'('valid XML name')
}
println writer.toString()
Output
<root> <first-name>Alice</first-name> <last-name>Johnson</last-name> <date-of-birth>1996-05-15</date-of-birth> <app.config version='2.0'>active</app.config> <_123data>valid XML name</_123data> </root>
When element names contain hyphens, dots, or other characters that aren’t valid Groovy identifiers, wrap them in quotes. Groovy’s metaprogramming resolves the quoted method name to an element name.
Best Practices Summary
DO:
- Use MarkupBuilder for simple XML creation – it’s simple and well-formatted
- Use StreamingMarkupBuilder when you need namespaces, CDATA, comments, or processing instructions
- Use XmlParser for parse-modify-serialize workflows
- Let the builders handle XML escaping – never escape manually
- Use
withWriter()when writing to files for proper resource cleanup
DON’T:
- Build XML with string concatenation – it’s fragile and insecure
- Forget to use quotes for element names with hyphens or dots
- Modify a node collection while iterating over it – collect first, then modify
Common Pitfalls
Pitfall 1: MarkupBuilder Method Name Conflicts
Method Name Conflicts
def writer = new StringWriter()
def xml = new groovy.xml.MarkupBuilder(writer)
xml.data {
// 'print' and 'println' conflict with MarkupBuilder methods
// Use quoted syntax to avoid conflicts
'print'('Print data')
'class'('MyClass') // 'class' is a reserved keyword in Groovy
'yield'('Some value') // Another potential conflict
}
println writer.toString()
Output
<data> <print>Print data</print> <class>MyClass</class> <yield>Some value</yield> </data>
If your XML element names clash with Groovy keywords or existing methods on MarkupBuilder (like print, class, yield), use the quoted syntax. This forces Groovy to treat it as a builder element call rather than a language construct.
Pitfall 2: Forgetting the Writer for MarkupBuilder
Writer Pitfall
// Without explicit writer - outputs to System.out
println "--- Prints to console ---"
def xml1 = new groovy.xml.MarkupBuilder() // defaults to System.out
xml1.item('Hello')
// With explicit writer - captures output
println "\n--- Captured in StringWriter ---"
def writer = new StringWriter()
def xml2 = new groovy.xml.MarkupBuilder(writer)
xml2.item('Hello')
println "Captured: ${writer.toString()}"
Output
--- Prints to console --- <item>Hello</item> --- Captured in StringWriter --- Captured: <item>Hello</item>
When you create MarkupBuilder without a Writer argument, it defaults to System.out. This is fine for debugging but not for production. Always pass a StringWriter or FileWriter to capture the output.
Pitfall 3: XmlParser node.value vs text()
value vs text Pitfall
def xmlText = '<root><item>Hello World</item></root>'
def root = new XmlParser().parseText(xmlText)
// text() returns the text content as a String
println "text(): ${root.item[0].text()}"
println "text() type: ${root.item[0].text().getClass().name}"
// value() returns the children list (which contains the text as a String)
println "value(): ${root.item[0].value()}"
println "value() type: ${root.item[0].value().getClass().name}"
// To SET text, assign to value (not text)
root.item[0].value = 'Updated Value'
println "After update: ${root.item[0].text()}"
Output
text(): Hello World text() type: java.lang.String value(): [Hello World] value() type: java.util.ArrayList After update: Updated Value
text() returns a String; value() returns the children list (which includes text content and child nodes). To modify text content, assign to value – assigning a plain String replaces the children list with that text.
Conclusion
We covered three core techniques for working with XML in Groovy: MarkupBuilder for simple XML creation, StreamingMarkupBuilder for namespace-heavy and feature-rich XML, and XmlParser for parse-modify-serialize workflows. Together with XmlSlurper for parsing, you now have the complete toolkit for any XML task in Groovy.
The key rules: use MarkupBuilder when you’re creating XML from scratch without namespaces, StreamingMarkupBuilder when you need namespaces or CDATA, and XmlParser when you need to modify existing XML. Never build XML with string concatenation – let the builders handle escaping and formatting.
To review the parsing side, see Groovy XmlSlurper – Parse XML the Easy Way and XmlParser vs XmlSlurper – Which to Use.
Summary
- MarkupBuilder writes XML directly to a Writer – simple, well-formatted, no namespace support
- StreamingMarkupBuilder creates deferred markup – supports namespaces, CDATA, comments, and PIs
- XmlParser + XmlUtil handles parse-modify-serialize workflows
- Builders escape special characters automatically – safer than string concatenation
- Use quoted method names for elements with hyphens, dots, or Groovy keyword conflicts
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 File Read, Write, and Delete Operations
Frequently Asked Questions
What is the difference between MarkupBuilder and StreamingMarkupBuilder in Groovy?
MarkupBuilder writes XML immediately to a Writer and is simpler to use. StreamingMarkupBuilder creates a deferred markup template using bind() that is serialized later. StreamingMarkupBuilder supports namespaces, CDATA sections, XML comments, and processing instructions, which MarkupBuilder does not.
How do I create XML from a Groovy Map or List?
Use MarkupBuilder with loops inside the closure. Iterate over your collection with each() and call builder methods to create elements dynamically. For example: xml.items { myList.each { item -> xml.item(item.name) } }. The builder converts each method call into an XML element.
How do I modify existing XML in Groovy?
Parse the XML with XmlParser (not XmlSlurper) to get mutable Node objects. Then use appendNode() to add children, remove() to delete nodes, and direct attribute assignment (node.@attr = ‘value’) to change attributes. Serialize back with XmlUtil.serialize() or XmlNodePrinter.
Does MarkupBuilder automatically escape special XML characters?
Yes. MarkupBuilder automatically escapes <, >, &, quotes, and apostrophes in both element text and attribute values. This prevents broken XML and XML injection vulnerabilities. Never manually escape when using builders.
How do I add XML namespaces with Groovy builders?
Use StreamingMarkupBuilder with the namespaces property. Register prefixes with namespaces << [prefix: 'uri'], then use quoted method names like ‘prefix:element’. MarkupBuilder does not have built-in namespace support, so use StreamingMarkupBuilder for namespace-heavy XML.
Related Posts
Previous in Series: Groovy XmlParser vs XmlSlurper – Which to Use
Next in Series: Groovy File Read, Write, and Delete Operations
Related Topics You Might Like:
- Groovy XmlSlurper – Parse XML the Easy Way
- Groovy XmlParser vs XmlSlurper – Which to Use
- Groovy findAll Method – Filter Collections Like a Pro
This post is part of the Groovy & Grails Cookbook series on TechnoScripts.com

No comment