Groovy substring extraction with 12 examples covering substring(), subscript operator, take(), drop(), and negative indices. Tested on Groovy 5.x.
“Extracting the right piece of a string is like cutting a diamond – precision matters, and the right tool makes all the difference.”
Donald Knuth, The Art of Computer Programming
Last Updated: March 2026 | Tested on: Groovy 5.x, Java 17+ | Difficulty: Beginner to Intermediate | Reading Time: 16 minutes
When you’re working with strings in Groovy, one of the most common things you’ll need to do is extract a part of a string – a groovy substring. Maybe you need the domain from an email address, the file extension from a path, or just the first few characters of a long piece of text. Whatever the case, Groovy gives you several ways to do it, and most of them are far more elegant than what Java offers.
If you’ve used Java’s substring() method, you already know the basics. But Groovy takes string extraction to another level with its subscript operator, range-based indexing, negative indices, and GDK methods like take() and drop(). This post covers all of them with tested, working examples.
If you haven’t read the foundational post yet, start with our Groovy String Tutorial – The Complete Guide. For related operations, also check out Groovy take() and Groovy reverse().
Table of Contents
What Is a Groovy Substring?
A groovy substring is simply a portion of an existing string. You specify where to start (and optionally where to stop), and Groovy hands you back a new string containing just those characters. Unlike some languages where substring operations are confusing, Groovy makes this surprisingly intuitive.
According to the official Groovy GDK documentation, Groovy enhances Java’s String class with additional methods and operator overloading that make substring operations cleaner and more readable.
Here’s what you can use for groovy string extract operations:
substring(beginIndex)– inherited from Java, returns everything from the given index to the endsubstring(beginIndex, endIndex)– inherited from Java, returns characters between the two indicesstr[index]– Groovy’s subscript operator for single character accessstr[range]– Groovy’s subscript operator with ranges for slicingtake(n)– get the first N characters (safe, never throws)drop(n)– skip the first N characters, return the restgetAt(index)andgetAt(range)– the method behind the subscript operator
Substring Methods at a Glance
| Method | Syntax | Returns | Throws on Out-of-Bounds? |
|---|---|---|---|
| substring(begin) | str.substring(5) | From index 5 to end | Yes |
| substring(begin, end) | str.substring(0, 5) | From index 0 to 4 | Yes |
| Subscript [index] | str[3] | Single character at index 3 | Yes |
| Subscript [range] | str[0..4] | Characters 0 through 4 (inclusive) | Yes |
| Negative index | str[-3..-1] | Last 3 characters | Yes |
| take(n) | str.take(5) | First 5 characters | No (safe) |
| drop(n) | str.drop(3) | Everything after first 3 | No (safe) |
| getAt(range) | str.getAt(0..4) | Same as str[0..4] | Yes |
Notice that take() and drop() are the only safe methods – they won’t throw an exception if you ask for more characters than the string has. We’ll see this in action shortly.
12 Practical Groovy Substring Examples
Example 1: Basic substring() – From Index to End
What we’re doing: Using Java’s substring(beginIndex) method to get everything from a given position to the end of the string.
Example 1: substring(beginIndex)
def text = "Hello, Groovy World!"
// Get everything from index 7 onwards
def result1 = text.substring(7)
println "From index 7: '${result1}'"
// Get everything from index 0 (the whole string)
def result2 = text.substring(0)
println "From index 0: '${result2}'"
// Get from the last word
def spaceIndex = text.lastIndexOf(' ')
def lastWord = text.substring(spaceIndex + 1)
println "Last word: '${lastWord}'"
Output
From index 7: 'Groovy World!' From index 0: 'Hello, Groovy World!' Last word: 'World!'
What happened here: substring(7) returns everything starting from index 7 (the ‘G’ in “Groovy”). The index is zero-based, so index 0 is ‘H’, index 1 is ‘e’, and so on. Combining lastIndexOf() with substring() is a common pattern for extracting the last word or segment.
Example 2: substring() – With Begin and End Index
What we’re doing: Using substring(beginIndex, endIndex) to extract a specific range of characters.
Example 2: substring(begin, end)
def text = "Hello, Groovy World!"
// Extract "Groovy" (index 7 to 13)
def groovy = text.substring(7, 13)
println "Extracted: '${groovy}'"
// Extract "Hello"
def hello = text.substring(0, 5)
println "Extracted: '${hello}'"
// Extract "World"
def world = text.substring(14, 19)
println "Extracted: '${world}'"
// Length of substring = endIndex - beginIndex
println "Length check: ${groovy.length()} == ${13 - 7}"
Output
Extracted: 'Groovy' Extracted: 'Hello' Extracted: 'World' Length check: 6 == 6
What happened here: The two-argument substring(begin, end) returns characters from begin (inclusive) to end (exclusive). This is the same behavior as Java – the end index is NOT included in the result. The resulting string’s length is always endIndex - beginIndex.
Example 3: Groovy Subscript Operator – Single Character
What we’re doing: Using Groovy’s [] subscript operator to access individual characters – much cleaner than Java’s charAt().
Example 3: Subscript Operator – Single Character
def text = "Groovy"
// Access individual characters
println "First character: ${text[0]}"
println "Third character: ${text[2]}"
println "Last character: ${text[5]}"
// Negative indices count from the end
println "Last character (negative): ${text[-1]}"
println "Second to last: ${text[-2]}"
println "Third to last: ${text[-3]}"
// Type check - returns a String, not a char!
println "Type: ${text[0].getClass().name}"
Output
First character: G Third character: o Last character: y Last character (negative): y Second to last: v Third to last: o Type: java.lang.String
What happened here: Groovy’s subscript operator [] is syntactic sugar for the getAt() method. Unlike Java’s charAt() which returns a char, Groovy’s [] returns a String. And here’s the fun part – negative indices count backwards from the end, so text[-1] gives you the last character without knowing the string’s length.
Example 4: Subscript Operator with Ranges – Groovy Substring Slicing
What we’re doing: Using Groovy’s range operator inside subscript brackets for flexible groovy substring slicing.
Example 4: Subscript with Ranges
def text = "Hello, Groovy World!"
// Inclusive range: characters 0 through 4
println "text[0..4]: '${text[0..4]}'"
// Extract "Groovy" using range
println "text[7..12]: '${text[7..12]}'"
// Negative range: last 6 characters
println "text[-6..-1]: '${text[-6..-1]}'"
// Mixed: from index 7 to 3rd from end
println "text[7..-8]: '${text[7..-8]}'"
// Reverse range: reverses the substring!
println "text[4..0]: '${text[4..0]}'"
// Exclusive range with ..<
println "text[0..<5]: '${text[0..<5]}'"
Output
text[0..4]: 'Hello' text[7..12]: 'Groovy' text[-6..-1]: 'World!' text[7..-8]: 'Groovy' text[4..0]: 'olleH' text[0..<5]: 'Hello'
What happened here: This is where Groovy really shines for groovy string extract operations. The range 0..4 is inclusive on both ends (unlike substring() where end is exclusive). You can use ..< for an exclusive end. Negative indices work inside ranges too. And if you reverse the range (like 4..0), Groovy reverses the result – a neat trick.
Example 5: take() – Get First N Characters Safely
What we’re doing: Using the GDK’s take() method to safely extract the first N characters without worrying about index bounds. For more details, see our dedicated Groovy take() post.
Example 5: take() – Safe First N Characters
def text = "Groovy Programming"
// Take first 6 characters
println "take(6): '${text.take(6)}'"
// Take first 3 characters
println "take(3): '${text.take(3)}'"
// Take more than string length - no exception!
println "take(100): '${text.take(100)}'"
// Take 0 characters
println "take(0): '${text.take(0)}'"
// Compare with substring (which would throw)
try {
text.substring(0, 100)
} catch (StringIndexOutOfBoundsException e) {
println "substring(0, 100) threw: ${e.class.simpleName}"
}
// take() is safe - returns what it can
println "Safe take: '${text.take(100)}'"
Output
take(6): 'Groovy' take(3): 'Gro' take(100): 'Groovy Programming' take(0): '' substring(0, 100) threw: StringIndexOutOfBoundsException Safe take: 'Groovy Programming'
What happened here: take(n) grabs the first N characters, but if N is larger than the string’s length, it just returns the entire string instead of throwing an exception. This makes it perfect for situations where you don’t know the input length ahead of time – truncating user display names, preview text, or log output.
Example 6: drop() – Skip First N Characters
What we’re doing: Using drop() to remove the first N characters and return the rest. Think of it as the opposite of take().
Example 6: drop() – Skip First N Characters
def text = "Groovy Programming"
// Drop first 7 characters
println "drop(7): '${text.drop(7)}'"
// Drop first character
println "drop(1): '${text.drop(1)}'"
// Drop more than string length - returns empty, no exception
println "drop(100): '${text.drop(100)}'"
// Drop 0 - returns entire string
println "drop(0): '${text.drop(0)}'"
// Combine take and drop to extract middle
def middle = text.drop(3).take(3)
println "drop(3).take(3): '${middle}'"
// take + drop always reconstructs the original
def first = text.take(7)
def rest = text.drop(7)
println "Reconstructed: '${first + rest}'"
Output
drop(7): 'Programming' drop(1): 'roovy Programming' drop(100): '' drop(0): 'Groovy Programming' drop(3).take(3): 'ovy' Reconstructed: 'Groovy Programming'
What happened here: drop(n) removes the first N characters. Just like take(), it’s safe – dropping more characters than available just returns an empty string. The combo of drop(n).take(m) is a really handy pattern for extracting a substring from the middle without doing index math.
Example 7: Negative Indices – Count from the End
What we’re doing: Using negative indices to access characters and extract substrings from the end of a string.
Example 7: Negative Indices
def filename = "report_2026_final.pdf"
// Last 4 characters (file extension with dot)
println "Extension: '${filename[-4..-1]}'"
// Last 3 characters (extension without dot)
println "Ext only: '${filename[-3..-1]}'"
// Everything except last 4 characters
println "Without ext: '${filename[0..-5]}'"
// Last character
println "Last char: '${filename[-1]}'"
// Negative indices on a URL
def url = "https://technoscripts.com/groovy-substring"
println "Last segment: '${url[url.lastIndexOf('/')..-1]}'"
// Get last N characters using a method
def lastN = { str, n -> str[-n..-1] }
println "Last 3 of 'Groovy': '${lastN('Groovy', 3)}'"
Output
Extension: '.pdf' Ext only: 'pdf' Without ext: 'report_2026_final' Last char: f Last segment: '/groovy-substring' Last 3 of 'Groovy': 'ovy'
What happened here: Negative indices are one of Groovy’s best features for groovy substring work. -1 is the last character, -2 is second to last, and so on. The range [-4..-1] gives you the last 4 characters. You can even mix positive and negative: [0..-5] means “from the beginning to 5th from the end.”
Example 8: getAt() – The Method Behind the Subscript
What we’re doing: Using getAt() directly – the method that Groovy calls when you use the [] operator.
Example 8: getAt() Method
def text = "Groovy Rocks!"
// getAt with single index - same as text[0]
println "getAt(0): '${text.getAt(0)}'"
// getAt with range - same as text[0..5]
println "getAt(0..5): '${text.getAt(0..5)}'"
// getAt with negative index
println "getAt(-1): '${text.getAt(-1)}'"
// getAt with a collection of indices
println "getAt([0, 2, 4, 6]): '${text.getAt([0, 2, 4, 6])}'"
// Useful for programmatic access
def indices = [0, 7, 8, 9, 10, 11]
def extracted = indices.collect { text.getAt(it) }.join('')
println "Collected: '${extracted}'"
Output
getAt(0): 'G' getAt(0..5): 'Groovy' getAt(-1): '!' getAt([0, 2, 4, 6]): 'Gov ' Collected: 'GRocks'
What happened here: Every time you write text[0..5], Groovy translates it to text.getAt(0..5). You can also pass a list of individual indices to getAt(), which is useful when you need characters at non-contiguous positions. This is handy for extracting characters based on a computed pattern.
Example 9: Extract First and Last N Characters
What we’re doing: Comparing different approaches to get the first and last N characters of a groovy substring.
Example 9: First and Last N Characters
def text = "Apache Groovy"
// First 6 characters - three ways
println "substring: '${text.substring(0, 6)}'"
println "range: '${text[0..5]}'"
println "take: '${text.take(6)}'"
// Last 6 characters - three ways
println "substring: '${text.substring(text.length() - 6)}'"
println "range: '${text[-6..-1]}'"
println "drop: '${text.drop(text.length() - 6)}'"
// First character
println "First: '${text[0]}'"
// Last character
println "Last: '${text[-1]}'"
// First 3 and last 3 combined
def preview = "${text.take(3)}...${text[-3..-1]}"
println "Preview: '${preview}'"
Output
substring: 'Apache' range: 'Apache' take: 'Apache' substring: 'Groovy' range: 'Groovy' drop: 'Groovy' First: 'A' Last: 'y' Preview: 'Apa...ovy'
What happened here: There are multiple ways to get the first or last N characters in Groovy. The take() method is safest for first-N, and the negative range [-n..-1] is the cleanest for last-N. The preview pattern ("${text.take(3)}...${text[-3..-1]}") is great for creating truncated displays.
Example 10: Extract Between Delimiters
What we’re doing: Extracting text between specific delimiters – brackets, tags, quotes, or custom markers.
Example 10: Extract Between Delimiters
// Extract between parentheses
def text1 = "Hello (World) Groovy"
def start1 = text1.indexOf('(') + 1
def end1 = text1.indexOf(')')
println "Between parens: '${text1.substring(start1, end1)}'"
// Extract between square brackets
def text2 = "Error [CODE_404] occurred"
def start2 = text2.indexOf('[') + 1
def end2 = text2.indexOf(']')
println "Between brackets: '${text2[start2..<end2]}'"
// Extract between two markers
def text3 = "START>>>payload data<<<END"
def startMarker = "START>>>"
def endMarker = "<<<"
def payload = text3.substring(
text3.indexOf(startMarker) + startMarker.length(),
text3.indexOf(endMarker)
)
println "Payload: '${payload}'"
// Extract all values between curly braces using findAll
def template = "Hello {name}, your {item} is ready at {location}"
def placeholders = template.findAll(/\{(\w+)\}/) { full, group -> group }
println "Placeholders: ${placeholders}"
// Reusable closure for between extraction
def between = { str, left, right ->
def s = str.indexOf(left)
def e = str.indexOf(right, s + left.length())
(s >= 0 && e >= 0) ? str.substring(s + left.length(), e) : null
}
println "Reusable: '${between('Say [hello] now', '[', ']')}'"
Output
Between parens: 'World' Between brackets: 'CODE_404' Payload: 'payload data' Placeholders: [name, item, location] Reusable: 'hello'
What happened here: Extracting between delimiters is one of the most common real-world substring operations. The pattern is always the same: find the start delimiter, find the end delimiter, then grab everything in between. The reusable closure version adds safety by checking if both delimiters exist before extracting. The findAll() with regex is perfect when you have multiple occurrences.
Example 11: Parsing URLs and Email Addresses
What we’re doing: Real-world groovy string extract operations on URLs and email addresses.
Example 11: Parsing URLs and Emails
// Parse URL components
def url = "https://technoscripts.com/groovy-substring-examples/?ref=home"
// Protocol
def protocol = url.substring(0, url.indexOf('://'))
println "Protocol: ${protocol}"
// Domain
def afterProtocol = url.substring(url.indexOf('://') + 3)
def domain = afterProtocol.substring(0, afterProtocol.indexOf('/'))
println "Domain: ${domain}"
// Path (without query string)
def path = afterProtocol.substring(afterProtocol.indexOf('/'))
if (path.contains('?')) {
path = path.substring(0, path.indexOf('?'))
}
println "Path: ${path}"
// Query string
def query = url.contains('?') ? url.substring(url.indexOf('?') + 1) : ''
println "Query: ${query}"
println "---"
// Parse email address
def email = "developer@technoscripts.com"
def username = email.substring(0, email.indexOf('@'))
def emailDomain = email.substring(email.indexOf('@') + 1)
def tld = emailDomain[emailDomain.lastIndexOf('.') + 1 .. -1]
println "Username: ${username}"
println "Domain: ${emailDomain}"
println "TLD: ${tld}"
Output
Protocol: https Domain: technoscripts.com Path: /groovy-substring-examples/ Query: ref=home --- Username: developer Domain: technoscripts.com TLD: com
What happened here: This is where groovy substring skills pay off in daily work. Parsing URLs and emails requires chaining indexOf() and substring() calls together. For production code, you’d probably use java.net.URI for URLs, but understanding how to do it with substrings is essential for custom parsing tasks where standard libraries don’t fit.
Example 12: Combining Substring Techniques
What we’re doing: Combining everything we’ve learned into practical, compound substring operations.
Example 12: Combined Techniques
// Truncate with ellipsis
def truncate = { str, max ->
str.length() <= max ? str : "${str.take(max - 3)}..."
}
println truncate("Groovy is amazing for string manipulation", 25)
println truncate("Short", 25)
// Mask credit card number
def card = "4532-1234-5678-9012"
def masked = "${'*' * 14}${card[-4..-1]}"
println "Masked: ${masked}"
// Extract file info from path
def filepath = "/home/user/documents/report_final_v2.pdf"
def filename = filepath[filepath.lastIndexOf('/') + 1 .. -1]
def name = filename[0 ..< filename.lastIndexOf('.')]
def ext = filename[filename.lastIndexOf('.') + 1 .. -1]
println "File: ${filename}"
println "Name: ${name}"
println "Extension: ${ext}"
// Pad and truncate for fixed-width formatting
def fixedWidth = { str, width ->
str.take(width).padRight(width)
}
println "|${fixedWidth('Groovy', 10)}|"
println "|${fixedWidth('A very long language name', 10)}|"
// Extract all words longer than 4 chars
def sentence = "The quick brown fox jumps over the lazy dog"
def longWords = sentence.split(' ').findAll { it.length() > 4 }
println "Long words: ${longWords}"
Output
Groovy is amazing for ... Short Masked: **************9012 File: report_final_v2.pdf Name: report_final_v2 Extension: pdf |Groovy | |A very lon| Long words: [quick, brown, jumps]
What happened here: These are patterns you’ll use in real projects. The truncate function uses take() for safety. Credit card masking uses string multiplication and negative indexing. File path parsing chains lastIndexOf() with the subscript operator. The fixed-width formatter combines take() with padRight(). All of these are bread-and-butter substring operations that come up regularly.
Safe Substring Operations
One of the biggest headaches with Java’s substring() is StringIndexOutOfBoundsException. Groovy provides several ways to avoid this pain.
Safe Substring Approaches
def text = "Groovy"
// UNSAFE: Java substring throws on bad indices
try {
text.substring(0, 100)
} catch (e) {
println "substring(0, 100): ${e.class.simpleName}"
}
// UNSAFE: subscript operator throws too
try {
text[0..100]
} catch (e) {
println "text[0..100]: ${e.class.simpleName}"
}
// SAFE: take() never throws
println "take(100): '${text.take(100)}'"
// SAFE: drop() never throws
println "drop(100): '${text.drop(100)}'"
// SAFE: Custom safe substring
def safeSubstring = { str, start, end = str.length() ->
def s = Math.max(0, Math.min(start, str.length()))
def e = Math.max(s, Math.min(end, str.length()))
str.substring(s, e)
}
println "safeSubstring(0, 100): '${safeSubstring(text, 0, 100)}'"
println "safeSubstring(-5, 3): '${safeSubstring(text, -5, 3)}'"
// SAFE: Null-safe with ?. operator
String nullStr = null
println "null?.take(5): '${nullStr?.take(5)}'"
Output
substring(0, 100): StringIndexOutOfBoundsException text[0..100]: StringIndexOutOfBoundsException take(100): 'Groovy' drop(100): '' safeSubstring(0, 100): 'Groovy' safeSubstring(-5, 3): 'Gro' null?.take(5): 'null'
The rule of thumb: if you’re not 100% sure about your input string’s length, use take() and drop() instead of substring() or the subscript operator. They’re designed to be forgiving.
Best Practice: Prefer
take(n)oversubstring(0, n)anddrop(n)oversubstring(n)when the input length is uncertain. Your code won’t crash at 2 AM because someone passed an empty string.
Real-World Substring Use Cases
Here are some additional patterns you’ll find useful in production Groovy code:
Real-World Substring Patterns
// 1. Strip prefix/suffix
def logLine = "[INFO] Server started on port 8080"
def message = logLine.drop(logLine.indexOf('] ') + 2)
println "Log message: ${message}"
// 2. Extract version number
def userAgent = "Mozilla/5.0 (compatible; Groovy/4.0.15)"
def version = userAgent[userAgent.indexOf('Groovy/') + 7 ..< userAgent.indexOf(')')]
println "Groovy version: ${version}"
// 3. CSV field extraction
def csv = "John,Doe,35,Engineer,New York"
def fields = csv.split(',')
println "Name: ${fields[0]} ${fields[1]}, Age: ${fields[2]}"
// 4. Sanitize input - keep only first 50 chars
def userInput = "This is a really long user input that could potentially be very very long and cause issues in the database"
def sanitized = userInput.take(50)
println "Sanitized: '${sanitized}'"
// 5. Extract domain from various URL formats
def urls = [
"https://www.example.com/page",
"http://blog.example.org",
"ftp://files.example.net/data"
]
urls.each { u ->
def d = u.substring(u.indexOf('://') + 3)
if (d.contains('/')) d = d.substring(0, d.indexOf('/'))
println "URL: ${u} → Domain: ${d}"
}
Output
Log message: Server started on port 8080 Groovy version: 4.0.15 Name: John Doe, Age: 35 Sanitized: 'This is a really long user input that could poten' URL: https://www.example.com/page → Domain: www.example.com URL: http://blog.example.org → Domain: blog.example.org URL: ftp://files.example.net/data → Domain: files.example.net
These patterns cover probably 90% of the substring work you’ll do in real Groovy projects. Log parsing, version extraction, input sanitization, and URL decomposition are things developers deal with daily.
Performance Considerations
For most cases, substring performance is not something you need to worry about. All of these methods run in essentially O(n) time where n is the length of the result. But there are a few things worth knowing:
substring()creates a new String object in modern JVMs (Java 7u6+). It doesn’t share the original’s char array anymore.take()anddrop()have a tiny overhead from the GDK method dispatch, but it’s negligible for normal usage.- The subscript operator
[]callsgetAt()which adds one method call of overhead compared to directsubstring(). - For tight loops over millions of strings,
substring()is marginally faster. For everything else, use whichever method reads best. - Avoid repeated substring operations on the same string in a loop – extract once and store the result.
Common Pitfalls
Pitfall 1: Inclusive vs Exclusive End Index
Inclusive vs Exclusive
def text = "Groovy"
// substring() end is EXCLUSIVE
println "substring(0, 3): '${text.substring(0, 3)}'" // Gro
// Range with .. is INCLUSIVE
println "text[0..3]: '${text[0..3]}'" // Groo
// Range with ..< is EXCLUSIVE (matches substring behavior)
println "text[0..<3]: '${text[0..<3]}'" // Gro
Output
substring(0, 3): 'Gro' text[0..3]: 'Groo' text[0..<3]: 'Gro'
This trips up a lot of developers. substring(0, 3) returns 3 characters (indices 0, 1, 2). But text[0..3] returns 4 characters (indices 0, 1, 2, 3) because the range is inclusive. Use ..< if you want the range to behave like substring().
Pitfall 2: Empty String Edge Cases
Empty String Edge Cases
def empty = ""
// take() is safe with empty strings
println "empty.take(5): '${empty.take(5)}'"
// drop() is safe too
println "empty.drop(5): '${empty.drop(5)}'"
// But subscript operator fails
try {
empty[0]
} catch (e) {
println "empty[0]: ${e.class.simpleName}"
}
// And substring fails
try {
empty.substring(0, 1)
} catch (e) {
println "empty.substring(0,1): ${e.class.simpleName}"
}
// Always check for empty first, or use take/drop
def safeFirst = { str -> str ? str[0] : '' }
println "safeFirst(''): '${safeFirst('')}'"
println "safeFirst('Hello'): '${safeFirst('Hello')}'"
Output
empty.take(5): ''
empty.drop(5): ''
empty[0]: StringIndexOutOfBoundsException
empty.substring(0,1): StringIndexOutOfBoundsException
safeFirst(''): ''
safeFirst('Hello'): 'H'
Empty strings are the silent killer in substring operations. Always either validate your input or use the safe methods (take()/drop()) when the input might be empty. Groovy’s truthiness check (if (str)) covers both null and empty strings.
Pitfall 3: Off-by-One with indexOf() and Substring
A common mistake when combining indexOf() with substring():
Off-by-One Errors
def text = "name=John"
// WRONG: includes the '=' sign
def wrong = text.substring(text.indexOf('='))
println "Wrong: '${wrong}'"
// RIGHT: skip past the '=' sign
def right = text.substring(text.indexOf('=') + 1)
println "Right: '${right}'"
// ALWAYS check if indexOf returns -1
def noEquals = "nameJohn"
def idx = noEquals.indexOf('=')
if (idx >= 0) {
println noEquals.substring(idx + 1)
} else {
println "No '=' found in '${noEquals}'"
}
Output
Wrong: '=John' Right: 'John' No '=' found in 'nameJohn'
Always remember that indexOf() returns the position of the delimiter itself. If you want the text after the delimiter, you need + 1 (or + delimiter.length() for multi-character delimiters). And always check for -1 to handle the case where the delimiter doesn’t exist.
Conclusion
We covered a lot of ground on groovy substring operations in this post – from the basic substring() inherited from Java, through Groovy’s elegant subscript operator and range-based slicing, to the safe take() and drop() methods that won’t blow up on bad input.
The Groovy way is almost always cleaner than the Java way. Where Java forces you to calculate indices carefully and handle exceptions, Groovy gives you negative indices, inclusive ranges, and safe extraction methods. Once you get comfortable with the subscript operator and ranges, you’ll rarely reach for substring() again.
If you want to go deeper into individual methods, check out the dedicated posts on take() and reverse().
Summary
- Use
str[0..4]instead ofstr.substring(0, 5)– it’s more readable and Groovy-idiomatic - Negative indices (
str[-3..-1]) let you grab from the end without knowing the length take(n)anddrop(n)are safe – they never throw on out-of-bounds- Remember:
..is inclusive,..<is exclusive – don’t mix them up withsubstring() - Always check
indexOf()for-1before using its result in a substring call
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 String take() – Get First N Characters
Frequently Asked Questions
How do I get a substring in Groovy?
You can use several methods: substring(beginIndex, endIndex) inherited from Java, the subscript operator str[0..4] for range-based extraction, take(n) to get the first N characters safely, or drop(n) to skip the first N characters. The subscript operator with ranges is the most Groovy-idiomatic approach.
What is the difference between substring() and the subscript operator in Groovy?
The main difference is how the end index works. substring(0, 5) treats the end index as exclusive (returns 5 characters). The range operator str[0..4] treats the end as inclusive (also returns 5 characters). Use str[0..<5] for exclusive-end behavior matching substring(). Also, substring() throws on invalid indices while take()/drop() are safe alternatives.
How do I extract the last N characters of a string in Groovy?
Use negative indices with the subscript operator: str[-3..-1] gives you the last 3 characters. This is cleaner than the Java approach of str.substring(str.length() - 3). Negative index -1 always refers to the last character, -2 to the second-to-last, and so on.
What is the safest way to extract a substring in Groovy without exceptions?
Use the take(n) and drop(n) methods from the Groovy GDK. take(n) returns the first N characters and never throws – if n is larger than the string length, it returns the entire string. drop(n) skips the first N characters and also never throws. Combine them as drop(start).take(length) for safe mid-string extraction.
Can I use negative indices with Groovy strings?
Yes! Groovy supports negative indices for string access. str[-1] returns the last character, str[-2] returns the second-to-last, and str[-3..-1] returns the last 3 characters. You can also mix positive and negative indices in ranges, like str[2..-3] to get characters from index 2 to the third-from-last position.
Related Posts
Previous in Series: Groovy String To Integer – All Conversion Methods
Next in Series: Groovy String take() – Get First N Characters
Related Topics You Might Like:
- Groovy String Tutorial – The Complete Guide
- Groovy String reverse() – Reverse a String
- Groovy String take() – Get First N Characters
This post is part of the Groovy & Grails Cookbook series on TechnoScripts.com

No comment