Simple Scripted Validators

The Simple Scripted Validator is a simple check for validating issues after they have been changed in a workflow transaction. You can validate that changes to multiple field are coherent according to your business processes, likewise attachments, issue links, and parent/child relationships. Here are some examples of how it can be used:

Require a fix version if the resolution value is fixed

This is the same as the longer-winded example here but using a simple scripted validator to avoid writing so much code:

groovy != "Fixed" || issue.fixVersions

Note that we invert what you might expect to write, to take advantage of short-circuit evaluation. Groovy and java will not evaluate the second clause if the first part is true - it’s unnecessary because true || false is true.

So what we’re saying if the resolution is not fixed, the transition is OK…​ otherwise the second part is evaluated which says that there must be at least one fixVersion.

You could instead write issue.fixVersions.size() > 0 but it’s clearer to take advantage of groovy truth, which says that a non-empty collection is evaluated as true in a Boolean context.

This is set up like this:

Results in:

Check the resolution value

Validate that a particular resolution value is chosen. You might want to combine this with a check on the action name.

issue.resolution?.name == 'Not an Issue'

Requiring a comment

This example requires a comment when the resolution selected is Won’t Fix:

issue.resolution?.name != "Won't Fix" || transientVars["comment"]
You can select the field to receive the error message according to what makes most sense to your users.

You can’t currently have the error message appear on the Comment field, so in this case you should probably not select any field so that your provided error message appears at the top of the form:

Requiring a comment when assignee changed

You can check for changed fields by calling methods on originalIssue, which is the issue as it was before modifications made during this transition have been applied.

So for example, to force a comment if the assignee has changed you could use:

originalIssue.assigneeId == issue.assigneeId || transientVars["comment"]

Make sure the message you provide is descriptive so as not to infuriate users. Also ask yourself if you really need to request a comment for this change.

Validating a multi-select option is selected

Multiselect values are a Collection of Option objects, so use:

cfValues['My Multi Select']*.value.contains("Some value")

This same code works for checkboxes fields.

For a single select (or a radio button field) use:

cfValues['My Single Select']?.value == "Some value"

A more complex example, where if the user user sets the select list field Demo to No, we require them to fill in a text field Reason for no demo:

cfValues['Demo']?.value != 'No' || cfValues['Reason for No Demo']

cfValues is a map-like structure where you can get the value of any custom field on the object. Note that the keys are the field name, and not id.

To validate for non-empty it’s sufficient to just do:

cfValues['Some Field']

Checking sibling subtasks

Here is a more complex example. We have a custom field SelectListA. We want to validate, on creation of a sub-task, that none of the other children of the parent have the same value for this particular custom field. You could imagine such a scenario in an "ordering process" or something.

import com.atlassian.jira.issue.customfields.option.Option

def customFieldManager = ComponentAccessor.getCustomFieldManager()
def cf = customFieldManager.getCustomFieldObjectByName("SelectListA")

if (!issue.isSubTask()) {
    return true

def parent = issue.parentObject
def selectedValue = issue.getCustomFieldValue(cf) as Option
!parent.subTaskObjects.any { subtask ->
    subtask.getCustomFieldValue(cf) == selectedValue

Although one-liners work fine, you can put any amount of code in the Condition field. If you don’t explicitly include a return, the result of the last statement executed defines whether the validator accepts or rejects the transition.

Validating Attachments

To check that at least one PDF file is attached:

issue.getAttachments().find { it.filename.endsWith(".pdf") }

The attachment must have existed previously on the issue before the transition was applied. If you have the Attachments field on the screen, you cannot get newly added attachments in this way.

If your workflow requires specific attachments at certain stages, your validator can either:

Subtask Relationships

In a transition you might want to validate properties of the current issues subtasks, or a subtask’s parent. For example, to ensure that all subtasks of this issue are resolved:

issue.subTaskObjects.every { it.resolution }

This will be true if there are no subtasks. To validate there is at least one resolved task:

issue.subTaskObjects && issue.subTaskObjects.every { it.resolution }

To test that at least one is in In Progress:

issue.subTaskObjects.any { == "In Progress" }

To check custom field values of subtasks, cfValues is not available, so you must use custom field manager to get hold of the custom field object. This is demonstrated in Checking sibling subtasks.

For subtasks, you can check properties of the parent by using parentObject. For example, to check whether the parent is in progress:

!issue.isSubTask() || == "Open"

The first clause is unnecessary if you are applying a workflow only to a subtask type(s). However if you don’t, calling parentObject.something on standard issue types will throw a NullPointerException.

Checking Linked Issues

Validate that the issue has at least one outward Duplicate link:


Perhaps combine this with a check for the issue being resolved as a Duplicate, e.g.:

groovy != "Duplicate" || issueLinkManager.getOutwardLinks(*'Duplicate')

If you have the Issue Links field on a form, and you want to validate links that have happened on this transition, follow the examples at Validating Links Added this Transition.

Validating Cascading Selects

Cascading selects will return you a Map<String,Option>. In the following examples, CascadingSelect is the name of a, you guessed it, cascading select field.

Validate that both drop-downs of a cascading select are filled:

cfValues["CascadingSelect"]?.keySet()?.size() == 2

Validate that the first dropdown of a cascading select is AAA, and the second is a1:

cfValues["CascadingSelect"]?.values()*.value == ["AAA", "a1"]

Check that the first option is AAA:

cfValues["CascadingSelect"]?.get(null)?.value == "AAA"

Check that the second option is a1:

cfValues["CascadingSelect"]?.get("1")?.value == "a1"