Every SOAP request, Jenkins config, and legacy data export is XML that somebody had to generate, and string concatenation breaks the moment a value contains an ampersand. This Groovy create XML guide shows the safer route: MarkupBuilder for simple documents, StreamingMarkupBuilder for namespaces and CDATA, and XmlParser with XmlUtil when you need to modify existing files. All 12 examples are tested on Groovy 5.0.6.
“Writing XML by hand is like writing assembly – you can do it, but there are better tools for the job.”
Last Updated: July 2026 | Tested on: Groovy 5.0.6, 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.
The diagram below shows how to pick the right tool: it branches on whether you are creating fresh XML or changing an existing document, then on whether you need namespaces or CDATA, so you land on MarkupBuilder, StreamingMarkupBuilder, or the XmlParser parse to modify to serialize path:
Tip: click the diagram to open it full screen, then use the zoom and pan controls for a closer look.
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 Groovy Create XML 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 <code> 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(). About those extra whitespace-only lines: XmlUtil.serialize(Node) pretty-prints an already indented rendering of the node, so on modern JDKs every structural line break gets an extra whitespace line next to it. It looks odd but the XML is still perfectly valid, and every parser ignores it.
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. One formatting quirk to know: XmlNodePrinter puts text content on its own indented line by default. If you prefer text inline with its tags, set printer.preserveWhitespace = true before calling print().
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. The whitespace-only lines come from XmlUtil.serialize() again, same as in Example 6, and they are harmless.
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:ws="http://example.com/webservice" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<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. Notice that the xmlns declarations on the root element do not come out in the order we registered them (ws appears before soap here). That is fine, declaration order carries no meaning in XML. 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 {
// Quoting handles reserved keywords, but watch what
// happens with 'print' - it is a real method conflict
'print'('Print data') // calls the script's print, no element!
'class'('MyClass') // 'class' is a reserved keyword in Groovy
'yield'('Some value') // quoting works fine here
}
println writer.toString()
Output
Print data<data> <class>MyClass</class> <yield>Some value</yield> </data>
Quoted syntax handles reserved keywords like class and yield without any trouble. But print is a real gotcha: inside the closure Groovy resolves 'print' against the enclosing script first, so “Print data” leaks to the console (look at the first line of the output) and no <print> element is created. Quoting does not help because a real print method exists and wins over the builder. If you genuinely need a <print> element, call the builder machinery directly with delegate.invokeMethod('print', 'Print data'), which produces <print>Print data</print> as expected.
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: groovy.util.NodeList After update: Updated Value
text() returns a String; value() returns the children list (a groovy.util.NodeList, 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 groovy create xml 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, and that loop-inside-builder shape is the everyday groovy create xml pattern in build scripts.
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.
Interview Questions on Creating and Modifying XML
“These come from real screens. Practice answering before you read each answer.”
Q: Inside a MarkupBuilder closure, one element simply never appears in the output, and it happens to share a name with a helper method defined in the same script. Coincidence?
No, that is the cause. Builder closures resolve names through the owner first by default, so a call like status('ok') finds your script’s status() method before the builder gets a chance to treat it as an element. The method runs, returns something, and no XML is written. Fixes: rename the helper, call the element through the builder variable explicitly like xml.status('ok'), or quote the element name. Understanding closure delegation order, owner versus delegate, is the underlying concept the interviewer is really probing.
Q: A template built with StreamingMarkupBuilder’s bind() renders different content than the data had at bind time. A teammate calls it a Groovy bug. What is actually happening?
bind() does not build the XML; it returns a Writable that executes your markup closure later, whenever it gets serialized. So the closure captures references, and if the underlying map or list changes between bind and toString, the output reflects the newer state. Verified on Groovy 5.0.6: mutate a bound map before writing and the mutation shows up in the XML. If you need a snapshot, copy the data into the closure or serialize immediately. This deferred-execution model is also why StreamingMarkupBuilder handles huge documents without holding them in memory.
Q: Partner data arrives with entities already encoded, and your generated XML now contains &. Where did the double escaping come from, and whose job is escaping anyway?
MarkupBuilder escapes every special character in the text you hand it, including the ampersand of an existing &, so pre-escaped input gets encoded twice. The rule: builders own escaping, so feed them raw, unescaped values and decode any pre-encoded input first. The reverse mistake is worse: manually escaping and concatenating strings invites malformed XML and injection when a value contains markup. One layer, exactly one, should do encoding; that principle applies to XML here the same way it applies to SQL parameters and HTML templates.
Q: To update one price in an order document, a script does order.customer[0].value = 'ACME' and suddenly the customer’s nested name and id elements are gone. Why?
On an XmlParser Node, value is the entire children list. Assigning a string to it replaces every child, nested elements included, with that single text value. Verified on 5.0.6: the customer node collapses to <customer>ACME</customer>. To change just the text of a leaf, target the leaf node itself, like customer.name[0].value = 'ACME', and to change attributes use node.@attr = ..., which is safe. Knowing that value means children, not text, is the difference between editing a document and deleting half of it.
Q: Generated XML declares encoding="UTF-8" but a customer in Germany reports broken umlauts. The code writes with new FileWriter(file). Diagnose it.
FileWriter uses the platform default charset, so on that server the bytes might be written as windows-1252 while the XML declaration promises UTF-8; any parser that believes the declaration then garbles non-ASCII characters. The fix is to make the writer match the declaration: file.withWriter('UTF-8') { w -> new MarkupBuilder(w)... } or an OutputStreamWriter with an explicit charset. This is a JVM-wide classic rather than a Groovy quirk, and naming the mismatch between declared and actual encoding is precisely what the interviewer wants to hear.
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