- Overview
- Supported constructs
- Java/Groovy classes
- GDK type methods
- Jira API access
- Sandbox constraints
- Next steps
Overview
The Groovy engine provides Groovy-like syntax for writing scripts in JiBrok Studio. It supports closures, GDK-style collection methods, Java date/time classes, JSON handling, and other Groovy/Java idioms. Scripts have access to the same Jira APIs as the JavaScript engine.
Select Groovy from the engine dropdown in the Script Console or script configuration to use this engine.
Note: The Groovy engine implements a Groovy-like syntax within the JiBrok sandbox. It is not a full Groovy/JVM runtime - see Sandbox Constraints for details.
Supported constructs
Variables and functions
// Dynamic typing with def
def name = "Hello"
def count = 42
// Type declarations
String message = "World"
int total = 0
// Functions
def greet(name) {
return "Hello, ${name}!"
}
// Default parameters
def createIssue(summary, priority = "Medium") {
return [summary: summary, priority: priority]
}
GString (String Interpolation)
def key = "PROJ-123"
def status = "Open"
log "Issue ${key} is ${status}"
log "Count: ${items.size()}"
// Multi-line strings
def description = """
Issue: ${key}
Status: ${status}
"""
Closures
// Basic closure
def square = { x -> x * x }
log square(5) // 25
// Closures with multiple params
def add = { a, b -> a + b }
// it - implicit single parameter
def doubled = items.collect { it * 2 }
// Method references
def names = issues.collect { it.summary }
Ranges
def range = 1..10 // inclusive: 1 to 10
def halfOpen = 1..<10 // exclusive: 1 to 9
for (i in 1..5) {
log i
}
// Range methods
(1..10).each { log it }
(1..10).collect { it * 2 }
Collections
// Lists
def list = [1, 2, 3, 4, 5]
def names = ["Alice", "Bob", "Charlie"]
// Maps
def map = [name: "PROJ-123", status: "Open"]
def config = ["key": value, "another": 42]
// Sets
def uniqueItems = [1, 2, 3] as Set
// GDK collection methods
list.each { log it }
list.collect { it * 2 }
list.findAll { it > 3 }
list.find { it > 3 }
list.any { it > 3 }
list.every { it > 0 }
list.groupBy { it % 2 == 0 ? "even" : "odd" }
list.sum()
list.unique()
Operators
// Safe navigation
def name = issue?.fields?.priority?.name
// Elvis operator
def value = input ?: "default"
// Spaceship operator (comparison)
items.sort { a, b -> a.key <=> b.key }
// Spread operator
def names = issues*.summary
// in operator
if ("bug" in labels) { log "Has bug label" }
// instanceof
if (value instanceof String) { log "Is string" }
Control flow
// if/else
if (priority == "High") {
log "Urgent"
} else if (priority == "Medium") {
log "Normal"
} else {
log "Low"
}
// switch/case
switch (status) {
case "Open":
log "New issue"
break
case "In Progress":
log "Working"
break
default:
log "Other"
}
// for loops
for (item in items) {
log item
}
for (def i = 0; i < 10; i++) {
log i
}
// while
while (count > 0) {
count--
}
// times, upto, downto
5.times { log "Repeat ${it}" }
1.upto(5) { log it }
5.downto(1) { log it }
Exception handling
try {
def result = Issues.get(key)
} catch (Exception e) {
log "Error: ${e.message}"
} finally {
log "Done"
}
throw new RuntimeException("Something went wrong")
assert items.size() > 0, "No items found"
Classes
class IssueProcessor {
String projectKey
def process(issues) {
return issues.findAll { it.key.startsWith(projectKey) }
}
}
def processor = new IssueProcessor(projectKey: "PROJ")
def filtered = processor.process(allIssues)
Java/Groovy classes
JSON handling
// Parse JSON
def slurper = new JsonSlurper()
def data = slurper.parseText('{"name": "test", "value": 42}')
log data.name // "test"
// Generate JSON
def json = JsonOutput.toJson([name: "test", value: 42])
def pretty = JsonOutput.prettyPrint(json)
| Class | Methods |
|---|---|
JsonSlurper |
parseText(text) |
JsonOutput |
toJson(obj), prettyPrint(json) |
Date and time
// LocalDate
def today = LocalDate.now()
def date = LocalDate.parse("2024-06-15")
def nextWeek = today.plusDays(7)
def formatted = today.format("dd MMM yyyy")
// LocalDateTime
def now = LocalDateTime.now()
def meeting = LocalDateTime.of(2024, 6, 15, 14, 30, 0)
log now.format("yyyy-MM-dd HH:mm")
// ZonedDateTime
def zonedNow = ZonedDateTime.now("America/New_York")
def utc = ZonedDateTime.now("UTC")
// Duration and Period
def duration = Duration.ofHours(2)
def period = Period.ofDays(30)
def between = Duration.between(start, end)
// Formatting
def fmt = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm")
log now.format(fmt)
// Business days
log today.isBusinessDay()
def nextBizDay = today.plusBusinessDays(5)
LocalDate
Class methods: now(), parse(str)
| Method | Description |
|---|---|
getYear() |
Get year |
getMonth() |
Get month (1-12) |
getDayOfMonth() |
Get day of month |
getDayOfWeek() |
Get ISO day of week (1-7) |
plusDays(n) / minusDays(n) |
Add/subtract days |
plusMonths(n) / minusMonths(n) |
Add/subtract months |
plusYears(n) / minusYears(n) |
Add/subtract years |
isAfter(other) / isBefore(other) |
Compare dates |
isEqual(other) |
Check equality |
format(pattern) |
Format with pattern |
jiraDate() |
Format as “2024-06-15” |
shortDate() |
Format as “6/15/24” |
mediumDate() |
Format as “Jun 15, 2024” |
longDate() |
Format as “June 15, 2024” |
fullDate() |
Format as “Saturday, June 15, 2024” |
isBusinessDay() |
Is Monday-Friday? |
plusBusinessDays(n) |
Add business days |
minusBusinessDays(n) |
Subtract business days |
startOfMonth() / endOfMonth() |
Month boundaries |
startOfYear() / endOfYear() |
Year boundaries |
LocalDateTime
Class methods: now(), of(year, month, day, hour, min, sec), parse(str)
Includes all LocalDate methods plus:
| Method | Description |
|---|---|
getHour() / getMinute() / getSecond() |
Time accessors |
plusHours(n) / minusHours(n) |
Add/subtract hours |
toLocalDate() |
Extract date part |
jiraDateTime() |
ISO-like date-time |
shortTime() |
Format as “2:30 PM” |
shortDateTime() |
Format as “6/15/24 2:30 PM” |
mediumTime() |
Format as “2:30:45 PM” |
mediumDateTime() |
Format as “Jun 15, 2024 2:30:45 PM” |
startOfDay() / endOfDay() |
Day boundaries |
ZonedDateTime
Class methods: now(zoneId), of(y, m, d, h, mi, s, zoneId), parse(str, formatter)
| Method | Description |
|---|---|
getYear(), getMonth(), getDayOfMonth() |
Date accessors |
getHour(), getMinute(), getSecond() |
Time accessors |
getZone() |
Get timezone |
plusDays(n) / minusDays(n) |
Add/subtract days |
plusHours(n) / minusHours(n) |
Add/subtract hours |
plusMinutes(n) / minusMinutes(n) |
Add/subtract minutes |
plusMonths(n) / minusMonths(n) |
Add/subtract months |
isAfter(other) / isBefore(other) |
Compare |
toLocalDate() |
Extract date |
toLocalDateTime() |
Extract date-time |
toInstant() |
Convert to Instant |
format(pattern) |
Format with pattern |
Other date/time classes
| Class | Methods |
|---|---|
Duration |
ofDays(n), ofHours(n), ofMinutes(n), ofSeconds(n), ofMillis(n), between(start, end) |
Period |
ofDays(n), ofWeeks(n), ofMonths(n), ofYears(n), between(start, end) |
Instant |
now(), ofEpochMilli(ms), ofEpochSecond(s) |
ChronoUnit |
DAYS, HOURS, MINUTES, SECONDS, MILLIS |
ZoneId |
of(id), systemDefault(), getAvailableZoneIds() |
DateTimeFormatter |
ofPattern(pattern), ISO_LOCAL_DATE, ISO_LOCAL_DATE_TIME, ISO_ZONED_DATE_TIME, ISO_INSTANT |
SimpleDateFormat |
create(pattern) |
Numeric wrappers
def n = Integer.parseInt("42")
def d = Double.parseDouble("3.14")
def isDigit = Character.isDigit('5' as char)
| Class | Methods |
|---|---|
Integer |
parseInt(str, radix), valueOf(str), MAX_VALUE, MIN_VALUE |
Long |
parseLong(str), valueOf(str), MAX_VALUE, MIN_VALUE |
Float |
parseFloat(str), valueOf(str), isNaN(v), isInfinite(v), isFinite(v) |
Double |
parseDouble(str), valueOf(str), isNaN(v), isInfinite(v), isFinite(v) |
Character |
isDigit(ch), isLetter(ch), isLetterOrDigit(ch), isUpperCase(ch), isLowerCase(ch), isWhitespace(ch), toUpperCase(ch), toLowerCase(ch) |
Collections utilities
Collections.sort(myList)
Collections.reverse(myList)
Collections.frequency(myList, "item")
Collections.min(myList)
Collections.max(myList)
Arrays.asList(1, 2, 3)
Arrays.sort(myArray)
| Class | Methods |
|---|---|
Collections |
sort(list), reverse(list), frequency(collection, obj), min(collection), max(collection), unmodifiableList(list), singletonList(obj), emptyList() |
Arrays |
asList(array), sort(array), binarySearch(array, key), fill(array, value), equals(a, b), toString(array) |
XML/HTML building and parsing
// Build XML
def builder = MarkupBuilder.create()
builder.element("root", [:], null) {
builder.element("item", [id: "1"], "First")
builder.element("item", [id: "2"], "Second")
}
log builder.toString()
// Parse XML
def slurper = XmlSlurper.create()
def root = slurper.parseText(xmlString)
log root.child("item").text()
log root.child("item").attr("id")
Cryptographic hashing
// Quick hash functions
def sha = Hash.sha256("my string")
def md5 = Hash.md5("my string")
// Full MessageDigest
def digest = MessageDigest.getInstance("SHA-256")
| Class | Methods |
|---|---|
Hash |
sha256(str), sha1(str), md5(str), sha512(str) |
MessageDigest |
getInstance(algorithm) - supports SHA-256, SHA-1, MD5, SHA-512 |
URL encoding
def encoded = URLEncoder.encode("hello world", "UTF-8") // "hello+world"
def decoded = URLDecoder.decode("hello+world", "UTF-8") // "hello world"
GDK type methods
The Groovy engine extends standard types with GDK-style methods.
String methods
In addition to standard string methods, the Groovy engine adds:
| Method | Description |
|---|---|
size() |
String length |
contains(str) |
Contains substring? |
padLeft(size, padding) |
Pad from left |
padRight(size, padding) |
Pad from right |
center(size, padding) |
Center in width |
count(str) |
Count occurrences |
minus(str) |
Remove first occurrence |
plus(str) |
Concatenate |
matches(regex) |
Matches pattern? |
toInteger() |
Parse to Integer |
toFloat() |
Parse to Float |
toLong() |
Parse to Long |
toDouble() |
Parse to Double |
take(n) |
First n characters |
drop(n) |
Skip first n characters |
toList() |
Convert to char list |
isNumber() |
Is numeric string? |
isInteger() |
Is integer string? |
stripIndent() |
Strip common indentation |
stripMargin(marginChar) |
Strip margin characters |
tokenize(delimiters) |
Tokenize string |
readLines() |
Split into lines |
List/array methods
GDK collection methods available on lists:
| Method | Description |
|---|---|
each(closure) |
Iterate elements |
eachWithIndex(closure) |
Iterate with index |
collect(closure) |
Transform elements (map) |
findAll(closure) |
Filter elements |
find(closure) |
First matching element |
any(closure) |
Any element matches? |
every(closure) |
All elements match? |
size() |
List size |
isEmpty() |
Is empty? |
contains(value) |
Contains value? |
first() / last() |
First/last element |
head() / tail() |
Head/rest of list |
flatten() |
Flatten nested lists |
unique() |
Remove duplicates |
plus(list) / minus(list) |
Add/remove elements |
intersect(list) |
Intersection |
disjoint(list) |
No common elements? |
sum(closure) |
Sum values |
max(closure) / min(closure) |
Max/min value |
count(closure) |
Count matching |
groupBy(closure) |
Group by key |
countBy(closure) |
Count by key |
collectEntries(closure) |
Transform to map |
collectMany(closure) |
FlatMap |
inject(initial, closure) |
Fold/reduce |
split(closure) |
Partition by predicate |
take(n) / drop(n) |
First n / skip n elements |
takeWhile(closure) / dropWhile(closure) |
Take/drop while predicate |
indexed() / withIndex() |
Add indices |
collate(size) |
Split into chunks |
tap(closure) |
Apply and return self |
Number methods
| Method | Description |
|---|---|
abs() |
Absolute value |
intdiv(divisor) |
Integer division |
power(exp) |
Power |
times(closure) |
Execute n times |
upto(to, closure) |
Iterate up to value |
downto(to, closure) |
Iterate down to value |
step(to, step, closure) |
Step iterate |
intValue() / longValue() |
Convert to int/long |
floatValue() / doubleValue() |
Convert to float/double |
Map methods
| Method | Description |
|---|---|
each(closure) |
Iterate entries |
collect(closure) |
Transform entries |
findAll(closure) |
Filter entries |
find(closure) |
First matching entry |
any(closure) / every(closure) |
Test entries |
get(key) / put(key, value) |
Access entries |
getOrDefault(key, default) |
Get with default |
remove(key) |
Remove entry |
size() / isEmpty() |
Size queries |
containsKey(key) / containsValue(value) |
Contains checks |
keySet() / values() / entrySet() |
Views |
subMap(keys) |
Sub-map by keys |
collectEntries(closure) |
Transform to map |
groupBy(closure) |
Group by key |
inject(initial, closure) |
Fold/reduce |
with(closure) |
Execute with map as delegate |
tap(closure) |
Apply and return self |
Closure methods
| Method | Description |
|---|---|
call(...args) |
Invoke closure |
curry(...args) |
Curry from left |
rcurry(...args) |
Curry from right |
ncurry(n, ...args) |
Curry at position n |
Jira API access
All Jira APIs are available in the Groovy engine:
// Search issues
def issues = Issues.search("project = PROJ AND status = Open")
issues.each { issue ->
log "${issue.key}: ${issue.summary}"
}
// Get and update an issue
def issue = Issues.get("PROJ-123")
issue.update([summary: "Updated title"])
// Create an issue
def newIssue = Issues.create("PROJ", "Task", [summary: "New task"])
// Use requestJira for low-level API calls
def response = requestJira("/rest/api/3/myself")
log response.displayName
// Process with closures
def highPriority = issues.findAll { it.priority == "High" }
def summaries = issues.collect { it.summary }
def grouped = issues.groupBy { it.status }
All built-in API namespaces (Issues, Users, Projects, Fields, Components, Versions, Boards, Sprints, DateUtils, CSV, Adf, Arrays, Strings, Validator, tables, queue, asyncEvent) work identically to the JavaScript engine.
Sandbox constraints
The Groovy engine runs within the same sandbox as the JavaScript engine. The same resource limits and security restrictions apply:
- No filesystem, network, or browser API access
- No
importof arbitrary Java/Groovy packages - only the listed classes are available - Resource limits (execution time, loop iterations, API calls) are enforced per trigger type
- Method whitelisting applies to all type methods
- No JVM access, no reflection, no class loading
Next steps
- Scripting Language - Language hub and engine comparison
- Python Engine - Python-like syntax engine
- Scripting API - Full API reference
- Scripting Examples - Practical recipes
- Script Console - Interactive editor
