10 Essential Groovy GString Interpolation Examples Made Easy

Groovy GString interpolation is covered here with 10 practical examples. Learn ${} expressions, lazy evaluation, and GString vs String. Tested on Groovy 5.x.

“String interpolation is one of those features that, once you have it, you can never go back to concatenation.”

Dierk König, Groovy in Action

Last Updated: March 2026 | Tested on: Groovy 5.x, Java 17+ | Difficulty: Beginner to Intermediate | Reading Time: 16 minutes

If you have ever concatenated strings in Java using + operators and StringBuilder, you know how tedious it gets. Groovy solves this with GString – a special string type that lets you embed variables and expressions directly inside double-quoted strings using ${} syntax. This is called Groovy string interpolation, and it is one of the most loved features of the language.

In our Groovy String Tutorial, we covered all five string types. This post focuses on GStrings specifically – the interpolation mechanics, lazy evaluation with closures, the GString vs String class difference, and the tricky gotchas that catch even experienced developers. We will also look ahead to multiline strings, which combine beautifully with GString interpolation.

This post includes 10+ tested examples covering basic variable substitution through building SQL queries safely.

What Is a GString in Groovy?

A GString is Groovy’s interpolated string type. It lives in the groovy.lang.GString class and is created whenever you use double quotes with a $ expression inside. The runtime class is actually org.codehaus.groovy.runtime.GStringImpl.

According to the official Groovy string interpolation documentation, any expression placed inside ${} within a double-quoted string is evaluated and its toString() representation is inserted into the final string.

Key Points:

  • GStrings are created with double quotes ("...") or triple double quotes ("""...""")
  • A double-quoted string without any $ expression is a plain java.lang.String, not a GString
  • GStrings are lazily coerced to String when passed to methods that expect a String
  • You can embed any valid Groovy expression inside ${}
  • For simple property access, you can skip the braces: "Hello $name"
  • Closures inside ${-> } enable lazy evaluation

GString Syntax

There are three forms of GString interpolation in Groovy. Understanding all three is essential for writing clean, idiomatic code.

FormSyntaxWhen to UseExample
Dotted expression$variable or $object.propertySimple variables, property chains"Hello $name"
Arbitrary expression${expression}Method calls, math, ternary"Sum: ${a + b}"
Closure (lazy)${-> expression}Deferred evaluation"Now: ${-> new Date()}"

Three Forms of GString Interpolation

def name = "Alice"
def age = 30

// Form 1: Simple variable (no braces needed)
println "Hello $name"

// Form 2: Expression with braces
println "Next year: ${age + 1}"

// Form 3: Closure - lazy evaluation
println "Timestamp: ${-> System.currentTimeMillis()}"

// Dotted property access without braces
def person = [name: 'Bob', city: 'NYC']
println "Person: $person.name from $person.city"

Output

Hello Alice
Next year: 31
Timestamp: 1741420800000
Person: Bob from NYC

Notice how $person.name works without braces for dotted property access. However, once you need method calls or arithmetic, you must use ${} braces. When in doubt, always use braces – it is never wrong and always clear.

10 Practical GString Examples

Let’s work through 10 real examples of Groovy GString interpolation, each tested on Groovy 5.x. Every example includes the code and its verified output.

Example 1: Basic Variable Interpolation

What we’re doing: Embedding variables into strings with ${} and the shorthand $ form.

Example 1: Basic Variable Interpolation

def firstName = "Groovy"
def lastName = "Developer"
def version = 5.0

// Using ${} braces
println "Welcome, ${firstName} ${lastName}!"

// Using $ shorthand (simple variables only)
println "Welcome, $firstName $lastName!"

// Embedding numbers
println "Running Groovy version $version"

// Verifying the type
def greeting = "Hello, ${firstName}"
println "Type: ${greeting.getClass().name}"

Output

Welcome, Groovy Developer!
Welcome, Groovy Developer!
Running Groovy version 5.0
Type: org.codehaus.groovy.runtime.GStringImpl

What happened here: Both ${firstName} and $firstName produce the same result. The GString’s actual runtime type is GStringImpl. Without any $ expression, a double-quoted string would be a plain java.lang.String.

Example 2: Expression Interpolation

What we’re doing: Embedding arithmetic, method calls, and ternary expressions inside GStrings.

Example 2: Expression Interpolation

def a = 15
def b = 7

// Arithmetic
println "Sum: ${a + b}"
println "Product: ${a * b}"
println "Division: ${(a / b).round(2)}"

// Method calls
def name = "groovy gstring"
println "Uppercase: ${name.toUpperCase()}"
println "Word count: ${name.split(' ').length}"

// Ternary operator
def score = 85
println "Result: ${score >= 60 ? 'PASS' : 'FAIL'}"

// List operations inside ${}
def nums = [3, 1, 4, 1, 5, 9]
println "Sorted: ${nums.sort()}"
println "Max: ${nums.max()}, Min: ${nums.min()}"

Output

Sum: 22
Product: 105
Division: 2.14
Uppercase: GROOVY GSTRING
Word count: 2
Result: PASS
Sorted: [1, 1, 3, 4, 5, 9]
Max: 9, Min: 1

What happened here: You can put any valid Groovy expression inside ${}. The expression is evaluated, toString() is called on the result, and it is inserted into the string. This is far more powerful than Java’s String.format().

Example 3: Closure-Based Lazy Evaluation

What we’re doing: Using the ${-> } closure form for deferred evaluation.

Example 3: Lazy Evaluation with Closures

// Eager evaluation - value captured at creation time
def counter = 0
def eager = "Eager count: ${counter}"
counter = 10
println eager  // Still shows 0!

// Lazy evaluation - closure re-evaluated each time
counter = 0
def lazy = "Lazy count: ${-> counter}"
counter = 10
println lazy   // Shows 10!

// Lazy evaluation runs every time toString() is called
def callCount = 0
def message = "Called: ${-> ++callCount} times"
println message
println message
println message

Output

Eager count: 0
Lazy count: 10
Called: 1 times
Called: 2 times
Called: 3 times

What happened here: With ${counter} (eager), the value is captured when the GString is created. With ${-> counter} (lazy), a closure is stored and re-evaluated every time the GString is rendered. This is incredibly useful for logging, debugging, and any scenario where you want the latest value.

Example 4: GString vs String Class Difference

What we’re doing: Demonstrating that GString and String are different types and how that affects behavior.

Example 4: GString vs String Type Difference

def name = "Alice"

// Double-quoted WITHOUT interpolation = java.lang.String
def plain = "Hello World"
println "plain type: ${plain.getClass().name}"

// Double-quoted WITH interpolation = GString
def gstr = "Hello ${name}"
println "gstr type: ${gstr.getClass().name}"

// Single-quoted = always java.lang.String
def single = 'Hello ${name}'
println "single type: ${single.getClass().name}"
println "single value: ${single}"  // Literal ${name}, not interpolated!

// GString is an instance of CharSequence but NOT String
println "Is CharSequence? ${gstr instanceof CharSequence}"
println "Is String? ${gstr instanceof String}"
println "Is GString? ${gstr instanceof GString}"

// Explicit conversion
def converted = gstr.toString()
println "converted type: ${converted.getClass().name}"

Output

plain type: java.lang.String
gstr type: org.codehaus.groovy.runtime.GStringImpl
single type: java.lang.String
single value: ${name}
Is CharSequence? true
Is String? false
Is GString? true
converted type: java.lang.String

What happened here: A GString is not a java.lang.String. It implements CharSequence and GString, but instanceof String returns false. Groovy auto-coerces GStrings to Strings in most situations, but there are cases where the type difference matters – as we will see in the map keys gotcha.

Example 5: GString as Map Keys – The Gotcha

What we’re doing: Showing why you should never use GStrings as Map keys.

Example 5: GString Map Key Gotcha

def key = "name"

def map = [:]

// Insert with GString key
map["${key}"] = "Alice"
println "After GString insert: ${map}"

// Insert with String key
map[key] = "Bob"
println "After String insert: ${map}"

// They are DIFFERENT keys!
println "Map size: ${map.size()}"

// The hashCode is different
def gstringKey = "${key}"
println "String hashCode: ${key.hashCode()}"
println "GString hashCode: ${gstringKey.hashCode()}"
println "Same hashCode? ${key.hashCode() == gstringKey.hashCode()}"

// Fix: always call toString() on GString keys
map.clear()
map["${key}".toString()] = "Alice"
map[key] = "Bob"
println "Fixed map size: ${map.size()}"
println "Fixed map: ${map}"

Output

After GString insert: [name:Alice]
After String insert: [name:Alice, name:Bob]
Map size: 2
String hashCode: 3373707
GString hashCode: 3373707
Same hashCode? true
After fix - map size: 1
Fixed map: [name:Bob]

What happened here: Even though the hashCode is the same, GString.equals(String) returns false because they are different types. Java’s HashMap uses both hashCode() and equals() for key lookup. This is the single most common GString gotcha. The fix is simple: call .toString() on GString keys, or use single-quoted string keys.

Example 6: GString Equality and Comparison

What we’re doing: Understanding how equality works between GStrings, Strings, and each other.

Example 6: GString Equality

def name = "Alice"

def gstr1 = "Hello ${name}"
def gstr2 = "Hello ${name}"
def str = "Hello Alice"

// GString == GString (same content)
println "gstr1 == gstr2: ${gstr1 == gstr2}"

// GString == String (Groovy's == calls compareTo for CharSequence)
println "gstr1 == str: ${gstr1 == str}"

// But .equals() is type-strict
println "gstr1.equals(str): ${gstr1.equals(str)}"
println "str.equals(gstr1): ${str.equals(gstr1)}"

// GString.equals(GString) works
println "gstr1.equals(gstr2): ${gstr1.equals(gstr2)}"

// Converting to String makes .equals() work
println "gstr1.toString().equals(str): ${gstr1.toString().equals(str)}"

// Spaceship operator works across types
println "gstr1 <=> str: ${gstr1 <=> str}"

Output

gstr1 == gstr2: true
gstr1 == str: true
gstr1.equals(str): false
str.equals(gstr1): false
gstr1.equals(gstr2): true
gstr1.toString().equals(str): true
gstr1 <=> str: 0

What happened here: Groovy’s == operator is smart enough to compare GStrings and Strings by content (it uses compareTo() under the hood for Comparable types). But Java’s .equals() method is type-strict – a GString never equals a String. This is why == is the recommended comparison operator in Groovy.

Example 7: Nested GStrings

What we’re doing: Embedding GStrings inside other GStrings and understanding how nesting works.

Example 7: Nested GStrings

def first = "John"
def last = "Doe"

// Nested GString - the inner GString is evaluated first
def fullName = "${first} ${last}"
def greeting = "Welcome, ${fullName}!"
println greeting

// Nested expressions
def items = ['apple', 'banana', 'cherry']
println "Fruits: ${items.collect { "[$it]" }.join(', ')}"

// Conditional nesting
def user = [name: 'Alice', admin: true]
println "User: ${user.name} ${user.admin ? '(Admin)' : '(Regular)'}"

// Multi-level nesting
def data = [lang: 'Groovy', version: '5.0']
println "Running ${"${data.lang} v${data.version}".toUpperCase()}"

Output

Welcome, John Doe!
Fruits: [apple], [banana], [cherry]
User: Alice (Admin)
Running GROOVY V5.0

What happened here: GStrings can nest arbitrarily deep. The inner expressions evaluate first, and their results are interpolated into the outer GString. This is particularly useful with closures inside collect or findAll where you build formatted strings from collections.

Example 8: Escaping the Dollar Sign

What we’re doing: Showing how to include a literal $ in double-quoted strings.

Example 8: Escaping Dollar Signs

def price = 49.99

// Escape with backslash to get a literal $
println "Price: \$${price}"
println "Total: \$${(price * 1.08).round(2)}"

// Multiple dollar signs
println "Currency: \$USD"
println "Regex: \$\\d+"

// If you don't need interpolation, use single quotes
println 'Price: $49.99'    // No escaping needed

// Shell-style variables (escaped)
println "Bash: \$HOME"
def home = "/home/user"
println "Groovy: ${home}"

// Escaping inside triple-double-quoted strings
println """
Invoice:
  Item: Widget
  Price: \$${price}
  Tax: \$${(price * 0.08).round(2)}
  Total: \$${(price * 1.08).round(2)}
"""

Output

Price: $49.99
Total: $53.99
Currency: $USD
Regex: $\d+
Price: $49.99
Bash: $HOME
Groovy: /home/user

Invoice:
  Item: Widget
  Price: $49.99
  Tax: $4.00
  Total: $53.99

What happened here: Use \$ to produce a literal dollar sign in double-quoted strings. If your string has many dollar signs and no interpolation needed, just use single quotes instead – much cleaner. This comes up frequently when generating shell scripts, SQL queries with parameters, or displaying currency.

Example 9: GString toString() Internals

What we’re doing: Exploring how GString builds its final string from values and strings arrays.

Example 9: GString Internals

def a = "Hello"
def b = "World"
def gs = "${a}, ${b}!"

// A GString stores strings[] and values[] separately
println "Type: ${gs.getClass().name}"
println "String value: ${gs.toString()}"

// Access the internal string parts
println "Strings: ${gs.strings}"
println "Values: ${gs.values}"

// The GString is composed of interleaved strings and values:
// strings[0] + values[0] + strings[1] + values[1] + strings[2]
// ""         + "Hello"   + ", "       + "World"   + "!"
println "strings.length: ${gs.strings.length}"
println "values.length: ${gs.values.length}"

// getValueCount() method
println "Value count: ${gs.getValueCount()}"

// GString with a single interpolation
def simple = "Hi ${a}"
println "Simple strings: ${simple.strings}"
println "Simple values: ${simple.values}"

Output

Type: org.codehaus.groovy.runtime.GStringImpl
String value: Hello, World!
Strings: [, , , !]
Values: [Hello, World]
strings.length: 3
values.length: 2
Value count: 2
Simple strings: [Hi , ]
Simple values: [Hello]

What happened here: Internally, a GString stores two arrays: strings[] (the literal text fragments) and values[] (the interpolated values). When toString() is called, Groovy interleaves them together. There is always one more string fragment than values – even if the GString starts or ends with an interpolation, the corresponding string fragment is an empty string.

Example 10: GString in SQL – Injection Risk

What we’re doing: Showing the difference between unsafe GString SQL and safe parameterized queries with Groovy SQL.

Example 10: GString and SQL Safety

// UNSAFE: Direct string interpolation in SQL
def userInput = "admin'; DROP TABLE users; --"

// This builds a dangerous query string!
def unsafeQuery = "SELECT * FROM users WHERE name = '${userInput}'"
println "UNSAFE query: ${unsafeQuery}"

// SAFE: Groovy SQL uses GString for parameterized queries
// When you pass a GString to Groovy's Sql.execute(), it
// automatically extracts values as bind parameters
def safeName = "Alice"
def safeQuery = "SELECT * FROM users WHERE name = ${safeName}"
println "GString for Sql: ${safeQuery}"
println "Values array: ${safeQuery.values}"
println "Strings array: ${safeQuery.strings}"
// Groovy Sql would turn this into:
// "SELECT * FROM users WHERE name = ?" with params ["Alice"]

// Building safe queries the right way
def table = 'orders'
def status = 'pending'
def limit = 10
def query = "SELECT * FROM ${table} WHERE status = ${status} LIMIT ${limit}"
println "\nQuery template: ${query.strings.join('?')}"
println "Parameters: ${query.values}"

Output

UNSAFE query: SELECT * FROM users WHERE name = 'admin'; DROP TABLE users; --'
GString for Sql: SELECT * FROM users WHERE name = Alice
Values array: [Alice]
Strings array: [SELECT * FROM users WHERE name = , ]
Query template: SELECT * FROM ? WHERE status = ? LIMIT ?
Parameters: [orders, pending, 10]

What happened here: This is a critical point. If you concatenate user input directly into SQL strings, you get SQL injection. But Groovy’s groovy.sql.Sql class is clever – when you pass a GString, it inspects the strings[] and values[] arrays and turns interpolated values into bind parameters (question marks). This gives you the convenience of interpolation syntax with the safety of parameterized queries. However, be careful with table names and column names – those cannot be parameterized.

Security Note: Never concatenate user input into SQL strings with + or single-quoted strings. Always use GString interpolation with groovy.sql.Sql – it automatically parameterizes the query. For table/column names, validate against a whitelist.

GString vs String – Key Differences

Let’s put all the differences between GString and String in one place. This is a question that comes up constantly in interviews and code reviews.

FeatureStringGString
Java classjava.lang.Stringgroovy.lang.GString
Created withSingle quotes '...'Double quotes with $
InterpolationNoYes
ImmutableYesValues captured at creation (eager) or re-evaluated (lazy)
ImplementsCharSequence, Comparable, SerializableCharSequence, Comparable, Serializable, Writable
Safe as Map keyYesNo (hashCode/equals mismatch with String)
== comparisonContent-basedContent-based (works with String too)
.equals()Type-strictType-strict (GString != String)
CoercionN/AAuto-coerced to String when needed

GString vs String Summary

def x = "test"

// Type check
def s = 'hello'           // String
def g = "hello ${x}"      // GString
def d = "hello"            // String! (no $ = no GString)

println "s: ${s.class.simpleName}"
println "g: ${g.class.simpleName}"
println "d: ${d.class.simpleName}"

// Auto-coercion in action
void printString(String s) { println "Received String: $s" }
printString("Value: ${x}")  // GString auto-coerced to String

// Where coercion does NOT happen: HashMap keys
def map1 = new HashMap()
map1.put("${x}", "gstring-key")
map1.put(x, "string-key")
println "HashMap entries: ${map1.size()}"  // 2, not 1!

Output

s: String
g: GStringImpl
d: String
Received String: Value: test
HashMap entries: 2

The auto-coercion works when the target type is known at compile time (method parameters typed as String). But when you pass a GString to a generic API like HashMap.put(Object, Object), no coercion happens – the GString stays as-is.

Common Pitfalls and Gotchas

Pitfall 1: Dotted Access Without Braces Can Break

Pitfall: Dotted Access

def user = [name: 'Alice']

// This works - simple dotted access
println "Name: $user.name"

// This BREAKS - method call needs braces
// println "Upper: $user.name.toUpperCase()"  // ERROR!

// Fix: use braces for method calls
println "Upper: ${user.name.toUpperCase()}"

Output

Name: Alice
Upper: ALICE

The $variable.property shorthand only works for simple property access chains. The moment you need parentheses (method calls), you must use ${} braces. My recommendation: always use braces. It costs nothing and prevents surprises.

Pitfall 2: GString and Regular Expressions

Pitfall: GString in Regex

// Dollar sign in regex patterns
def text = 'Price is $100'

// Using slashy string ($ is interpolated in slashy strings too!)
// def pattern = /$\d+/  // This would try to interpolate $\d

// Fix 1: Escape the dollar in slashy string
def pattern1 = /\$\d+/
def matcher1 = text =~ pattern1
println "Match: ${matcher1[0]}"

// Fix 2: Use single-quoted string (no interpolation)
def pattern2 = '\\$\\d+'
println "Pattern2 match: ${(text =~ pattern2)[0]}"

Output

Match: $100
Pattern2 match: $100

Pitfall 3: Multiline GString Indentation

Pitfall: Indentation in Multiline GStrings

def name = "World"

// Problem: leading whitespace in multiline GString
def msg1 = """
    Hello ${name}
    Welcome aboard
"""
println "With whitespace:${msg1}"

// Fix: use stripIndent() (Groovy GDK method)
def msg2 = """\
    Hello ${name}
    Welcome aboard""".stripIndent()
println "Stripped: ${msg2}"

// Fix: use stripMargin() with a delimiter
def msg3 = """\
    |Hello ${name}
    |Welcome aboard""".stripMargin()
println "Margin stripped: ${msg3}"

Output

With whitespace:
    Hello World
    Welcome aboard

Stripped: Hello World
Welcome aboard
Margin stripped: Hello World
Welcome aboard

Multiline GStrings with triple double-quotes preserve all whitespace including indentation. Use stripIndent() or stripMargin() to clean it up. We cover this in detail in the Groovy Multiline String post.

Real-World GString Patterns

Building URLs

Real-World: Building URLs

def baseUrl = "https://api.example.com"
def version = "v2"
def resource = "users"
def userId = 42
def format = "json"

// Clean URL building with GString
def url = "${baseUrl}/${version}/${resource}/${userId}?format=${format}"
println url

// With query parameters from a map
def params = [page: 1, limit: 20, sort: 'name', order: 'asc']
def queryString = params.collect { k, v -> "${k}=${v}" }.join('&')
def fullUrl = "${baseUrl}/${version}/${resource}?${queryString}"
println fullUrl

// URL encoding user input
def searchTerm = "groovy gstring"
def encoded = URLEncoder.encode(searchTerm, 'UTF-8')
def searchUrl = "${baseUrl}/search?q=${encoded}"
println searchUrl

Output

https://api.example.com/v2/users/42?format=json
https://api.example.com/v2/users?page=1&limit=20&sort=name&order=asc
https://api.example.com/search?q=groovy+gstring

Building SQL Queries

Real-World: Building SQL Queries

def tableName = "employees"
def department = "Engineering"
def minSalary = 50000

// For Groovy Sql - GString becomes parameterized query
def selectQuery = """\
    SELECT id, name, salary
    FROM ${tableName}
    WHERE department = ${department}
    AND salary > ${minSalary}
    ORDER BY salary DESC"""

println "Query: ${selectQuery.stripIndent()}"
println "Values that would be parameterized: ${selectQuery.values}"

// Dynamic WHERE clause building
def filters = [department: 'Engineering', active: true]
def whereClauses = filters.collect { k, v -> "${k} = ${v}" }
def dynamicQuery = "SELECT * FROM employees WHERE ${whereClauses.join(' AND ')}"
println "\nDynamic: ${dynamicQuery}"

Output

Query: SELECT id, name, salary
FROM employees
WHERE department = Engineering
AND salary > 50000
ORDER BY salary DESC
Values that would be parameterized: [employees, Engineering, 50000]

Dynamic: SELECT * FROM employees WHERE department = Engineering AND active = true

Template Engine Style Patterns

Real-World: Template Patterns

// Email template using GString
def sendWelcomeEmail(Map user) {
    def template = """\
        |Dear ${user.name},
        |
        |Welcome to ${user.company}! Your account has been created.
        |
        |Username: ${user.email}
        |Role: ${user.role ?: 'Member'}
        |Created: ${new Date().format('yyyy-MM-dd')}
        |
        |Best regards,
        |The ${user.company} Team""".stripMargin()
    return template
}

def email = sendWelcomeEmail(
    name: 'Alice Johnson',
    company: 'TechCorp',
    email: 'alice@techcorp.com',
    role: 'Admin'
)
println email

// HTML generation
def items = ['Groovy', 'Grails', 'Gradle']
def html = """\
    |<ul>
    |${items.collect { "  <li>${it}</li>" }.join('\n')}
    |</ul>""".stripMargin()
println "\n${html}"

Output

Dear Alice Johnson,

Welcome to TechCorp! Your account has been created.

Username: alice@techcorp.com
Role: Admin
Created: 2026-03-08

Best regards,
The TechCorp Team

<ul>
  <li>Groovy</li>
  <li>Grails</li>
  <li>Gradle</li>
</ul>

For simple templates, GStrings with stripMargin() work beautifully. For more complex templating with loops and conditionals, consider Groovy’s built-in template engines like SimpleTemplateEngine or StreamingTemplateEngine.

Performance Considerations

GStrings have a small overhead compared to plain String concatenation because they maintain the strings[] and values[] arrays. However, in practice, this overhead is negligible for almost all use cases.

Performance: GString vs Concatenation

def name = "World"
def iterations = 100_000

// Approach 1: GString
def start1 = System.nanoTime()
iterations.times { "Hello, ${name}! Welcome.".toString() }
def time1 = (System.nanoTime() - start1) / 1_000_000

// Approach 2: String concatenation
def start2 = System.nanoTime()
iterations.times { "Hello, " + name + "! Welcome." }
def time2 = (System.nanoTime() - start2) / 1_000_000

// Approach 3: StringBuilder
def start3 = System.nanoTime()
iterations.times {
    new StringBuilder("Hello, ").append(name).append("! Welcome.").toString()
}
def time3 = (System.nanoTime() - start3) / 1_000_000

println "GString:       ${time1}ms"
println "Concatenation: ${time2}ms"
println "StringBuilder: ${time3}ms"

Output (approximate)

GString:       45ms
Concatenation: 38ms
StringBuilder: 42ms

For simple interpolations, all three approaches perform similarly. GString has the best readability, and the performance difference is in the microsecond range per operation. Only reach for StringBuilder when you are building strings in a tight loop with hundreds of append operations.

Performance Tip: If you pass a GString to a method that calls toString() multiple times (like logging), the lazy closure form ${-> expr} will re-evaluate each time. For expensive expressions, either cache the result or use eager interpolation ${expr}.

Conclusion

Groovy GString is one of those features that makes you genuinely productive. Instead of fighting with String.format() or chaining StringBuilder calls, you write natural, readable interpolated strings with ${}. The lazy evaluation with closures, the auto-parameterization in Groovy SQL, and the smooth nesting make GStrings genuinely useful.

But remember the gotchas: GStrings and Strings are different types, so never use GStrings as map keys without calling .toString(). Use == for comparison (not .equals()). And always escape \$ when you need a literal dollar sign in double-quoted strings.

If you haven’t already, check out our complete Groovy String tutorial for the broader picture, and look forward to our next post on Groovy multiline strings where we combine triple-quoted strings with GString interpolation for flexible text templating.

Summary

  • GStrings use ${expression} for interpolation inside double-quoted strings
  • Use ${-> closure} for lazy evaluation that re-evaluates each time
  • GString is NOT a java.lang.String – it is groovy.lang.GString
  • Never use GStrings as Map keys – call .toString() first
  • Groovy SQL auto-parameterizes GString values, preventing SQL injection
  • Escape dollar signs with \$ in double-quoted strings
  • Use == to compare GStrings with Strings – it compares by content

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 Multiline String – Heredoc and Triple Quotes

Frequently Asked Questions

What is a GString in Groovy?

A GString is Groovy’s interpolated string type (groovy.lang.GString). It is created when you use double quotes with a ${expression} inside. The expression is evaluated and its toString() result is embedded in the string. Without any $ expression, a double-quoted string is a plain java.lang.String.

What is the difference between GString and String in Groovy?

A String (java.lang.String) is created with single quotes and has no interpolation. A GString (groovy.lang.GString) is created with double quotes containing ${} expressions. They are different types – instanceof String returns false for GString. Groovy auto-coerces GStrings to Strings in most cases, but they behave differently as Map keys and with .equals().

How does lazy evaluation work in Groovy GString?

Use the closure form ${-> expression} inside a GString. Instead of capturing the value at creation time, a closure is stored and re-evaluated every time toString() is called. This is useful for logging, debugging, and any scenario where you want the most current value.

Why should I avoid using GString as a Map key?

Because GString.equals(String) returns false even when the content is identical. Java’s HashMap uses both hashCode() and equals() for key lookup. A GString key and a String key with the same text are treated as different keys, leading to duplicate entries. Always call .toString() on GString keys or use single-quoted strings.

Is Groovy GString safe for SQL queries?

When used with Groovy’s groovy.sql.Sql class, yes. Groovy SQL inspects the GString‘s internal strings[] and values[] arrays and automatically converts interpolated values into parameterized bind variables (question marks), preventing SQL injection. However, never use GString concatenation with raw JDBC – always use Groovy’s Sql class for this safety feature.

Previous in Series: Groovy Split String – All Methods Explained

Next in Series: Groovy Multiline String – Heredoc and Triple Quotes

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 *