In this page

Why scripting?

Better PDF Exporter uses Velocity as its language to define PDF document templates. In the templates, Velocity logic statements are mixed with FO formatting tags to get the final result. Although these tools are powerful at what they do, it is still purely a template language, not a full-blown programming language. That may impose limitations when implementing more complex PDF document exports.

In many cases, this limitation is not a real problem, as your PDF document exports may not require any complex logic. You just want to display field values, do some formatting, make trivial if-then switches. To implement these, using Velocity alone is sufficient, and you should not complicate your life with scripting.

In other situations, the requirements for the logic that needs to be built into your templates are more complex. To implement these, Velocity alone may not be enough, you will need to do some scripting.

Don't worry, scripting is easy and it opens new horizons for your PDF documents!

When to use scripting?

Some use case examples that you can implement only with scripting (not in Velocity alone):

  • Draw charts. (Ex: generate graphical visualization in project status reports.)
  • Integrate with external resources. (Ex: integrate vendor information into your quotes queried from an external CRM database or an external webservice.)
  • Do precise arithmetic. (Ex: calculate precise money values in invoice documents.)
  • Access Jira internals. (Ex: execute a secondary saved filter to collect more data.)
  • Implement data processing algorithms using advanced data structures. (Ex: build dependency tables for traceability matrixes.)

What is Groovy?

Groovy is the primary scripting language supported by the Better PDF Exporter for Jira. Groovy is the de-facto standard agile scripting language for the Java platform, the same platform on which Jira runs.

What are the advantages of Groovy, compared to other scripting languages?

  • It is very easy to learn and use.
  • It is already known for Jira users, as many other Jira apps use Groovy to implement custom logic.
  • It beautifully integrates with Jira internals.
  • There are lots of sample code, documentation and answers available on the web.
  • It is mature and proven, having been used in mission critical apps at large organizations for years.

The basics of Groovy can be learnt literally in hours, assuming that you have some background in a modern programming language, like Java, Javascript or C++.

Useful resources:

Writing Groovy scripts

Your first script in 2 minutes

Here is the good old Hello world example, implemented with Groovy for the Better PDF Exporter for Jira.

First, save your logic to a Groovy script file hello-world.groovy:

// hello-world.groovy

helloWorld = new HelloWorldTool()

class HelloWorldTool {
	def say() {
		"Hello world!"

Then, execute this in your template hello-world-fo.vm:

## hello-world-fo.vm

## execute the script with the $scripting tool

## after executing the script, the object created by the script is available as "$helloWorld"
## let's call a method and put the greeting text to a text block
<fo:block><fo:inline font-weight="bold">Ex 1:</fo:inline> $helloWorld.say()</fo:block>

Tadaam! That's it. Now you have the text generated by the Groovy code in the PDF.

Tip: it is usually a good idea to follow the naming convention used above. If your template is an implementation of "my document type", then save the template to my-document-type-fo.vm and the script to my-document-type.groovy. It helps to see what files belong together.

Executing scripts efficiently with executeOnce()

(since Better PDF Exporter 7.4.0)

In addition to execute(), the scripting tool offers another method intuitively called executeOnce(). This second method guarantees that if the same script (identified by its filename) was passed to it multiple times, that will be executed only for the first time. All further method calls during the same PDF file rendering will immediately return.

See this example to understand the difference:

$scripting.execute("foo.groovy") ## executed for the second time
$scripting.execute("foo.groovy") ## executed for the third time

$scripting.executeOnce("bar.groovy") ## not executed
$scripting.executeOnce("bar.groovy") ## not executed

Okay, why should you use it?

It's all about being efficient and about keeping the template code organized and simple.

Executing scripts takes time. Although it is rather fast (around 100 milliseconds for most scripts shipped with the app), we want to execute scripts only if it is absolutely necessary. Plus, we want to make the execution as efficient as possible without complicating the Velocity template.

To achieve this, we follow this pattern (which was also used by the "Hello world" example above):

  1. We run the script in a late moment when we are 100% sure that it is necessary.
  2. We run the script only once.
  3. During that single execution, the Groovy script defines a class which implements the custom logic, and instantiates one object of that class.
  4. There is only one object instance required, which is made available for the Velocity template.

The executeOnce() helps us with the first and the second points. We don't need to check if the script was already executed in the Velocity code using flags. We can just call executeOnce() method any time, and it guarantees that the execution is not redundant.

Passing objects from templates to scripts

Besides the basics of script execution, it is important to learn how to share data and pass objects between templates and scripts.

Scripts have a dead-simple way to access the Velocity context objects used in templates. There is an important aspect of script execution, which is albeit transparent, yet important to understand. Before execution, each script is converted to an actual Groovy class in the background. (Our script hello-world.groovy will be converted to the class named hello-world, for instance.) Then, the Velocity context objects will be available as properties of that class! As the generated class is the "outermost" class in the script, its properties appear like "global variables" (more on this later). Simple, right?

Here is an example. You probably know that the currently signed-in Jira user is available as $user in the template code. At the same time, this is also available as the object user in Groovy!

// hello-world.groovy

helloUser = new HelloUserTool(user) // "user" is available from the Velocity context

class HelloUserTool {
	def user

	HelloUserTool(user) {
		this.user = user // store the argument for later use

	def say() {
		"Hello ${user.displayName}!" // use a property

	def say2(issues) {
		"Hello ${user.displayName}! You have ${issues.size()} issues." // use a property and a method argument

Let's greet him:

## hello-world-fo.vm

<fo:block><fo:inline font-weight="bold">Ex 2:</fo:inline> $helloUser.say()</fo:block>

You can also pass arguments to the Groovy methods with ease:

## hello-world-fo.vm

<fo:block><fo:inline font-weight="bold">Ex 3:</fo:inline> $helloUser.say2($issues)</fo:block>

The resulted PDF:

Final note: although from the above code it may feel like as if we had a global variable "user", this is not true. In fact, there is no such thing like "global" in Groovy! Read this article to avoid surprises.

Passing objects from scripts to templates

The recipe is simple: all so-called "binding variables" created in Groovy will be automatically available in the templates.

What is a binding variable? When it is not defined, it is in the binding, thus it will be available in templates, too.

bindingVar = "I am a binding variable" // will be available in the template
String localVar = "I am a local variable" // it will *not* be available in the template

Therefore, we recommend the following simple scheme:

  1. Implement your logic in lightweight Groovy classes.
  2. Instantiating them as binding variables in Groovy.
  3. Call their methods in the template.
You may also want to follow the naming convention of calling your tool classes SomethingTool and instantiating them called something, just as we did in the examples.

Scripting good practices

  1. Separation of concerns: clearly separate visuals and logic. Use Velocity for iterating, trivial if-then's, formatting, and use Groovy for implementing complex logic. Not vice versa!
  2. Follow the naming conventions suggested in this article.

Working with external Java classes and OSGi components

Groovy scripts frequently need to use Java classes and components from Jira core, bundled apps or other user-installed apps. This section explains how.

Importing external Java classes

(since Better PDF Exporter 9.0.0)

The Groovy execution engine uses a plugin-aware classloader. Therefore, if a class (from any other app!) is available anywhere in the plugin system, it can be imported natively:

import org.marvelution.jji.utils.JobHash // assumes that the "Jenkins Integration" app is installed

def jobHash = new JobHash()
def hash = jobHash.hash("my-string")

This comes with a hardly noticable performance penalty: the first export may take a bit longer, but next exports will be fast. They are faster due to caching the class loaded during the first export.

Accessing external OSGi components

(since Better PDF Exporter 9.0.0)

Jira apps typically expose their functionality and integration points (their API) via OSGi components. To integrate with an app, you can load its OSGi components with the ClassHelper utility tool.

This example loads a component from the "Elements Connect" app (assuming that it is installed):

import com.midori.jira.plugin.commons.util.ClassHelper

// load the component by its full class name
// don't define the type for the returned object
def ds = ClassHelper.getOSGiComponentInstanceOfType("com.valiantys.nfeed.api.IFieldDisplayService")

// we have a working service, yay!
def result = ds.getDisplayResult(issueId, customFieldId)

Don't forget that the OSGi container uses a different classloader than the Groovy execution engine. That's why you can't cast the OSGi components to their precise type! In Groovy, an "optionally typed" language, it is not even necessary. Just use def!

In some very rare cases, you may want to access a component which has multiple versions available in the OSGi container. If you pass also the app key, ClassHelper will search for the class only in that app:

import com.midori.jira.plugin.commons.util.ClassHelper

// pass the app key as the first parameter (to use the classloader of that specific app)
def ds = ClassHelper.getOSGiComponentInstanceOfType("com.valiantys.jira.plugins.SQLFeed", "com.valiantys.nfeed.api.IFieldDisplayService")

This technique is rarely needed, but it is worth a try if you have problems with the first, simpler technique.

Legacy approach

This section primiarily applies to pre-9.0.0 app versions. Although the technique described here also works in modern app versions, it is unnecessarily complicated.

There are times when you fail to reference a Java (Groovy) class by using an import statement, as the class is made available for apps by Jira. For example, you'd like to get the JqlQueryParser component from Jira, but the Groovy interpreter cannot import its class.

The problem is due to Jira running apps in an OSGi environment with controlled classloading. Groovy scripts run in the same environment as the app that executes those, therefore the same limits affect scripts, too.

Luckily, Groovy being a dynamic language, there is a clever trick to overcome this:

// load the class by name using the class-loader of a Jira-internal class
def clazz = ComponentAccessor.class.classLoader.loadClass("com.atlassian.jira.jql.parser.JqlQueryParser")

// don't define the type when getting the component
def jqlQueryParser = ComponentAccessor.getOSGiComponentInstanceOfType(clazz)

// we have a working query parser, yay!
def query = jqlQueryParser.parseQuery("project = CONTRACTOR and assignee = ${}")

Practical scripting

Logging from scripts

Logging from a script can be useful in a number of cases, like providing debug information or signaling exceptional conditions with warning-type log lines. In order to write to the Jira log, you have to use log4j, the logging library also used by Jira itself.


  1. Import the Logger class (in the top of the Groovy script file):
    import org.apache.log4j.Logger
  2. Create a Logger object in your class:
    public class MyClass {
    	def log = Logger.getLogger(this.getClass())
    	// ...
  3. After these, you can write to the Jira system log like this:
    // this goes to the Jira system log
    log.error("User not found!")
    Please note that when your script writes to the Jira log, the log lines can be filtered out or formatted by the log4j configuration, like any "regular" log line.

As a low-cost alternative, you can also write directly to the system console:

// this goes to the System (java) console
System.out.println("Hello console")

This trick should only be used for quick verifications, if you have access to the console (typically in development environments).

Unit testing scripts

Writing unit testing in Groovy is easy. For practical reasons, we recommend putting your unit test(s) into the file of the tested Groovy class, unless that grows inconveniently large. This helps to keep them neatly packaged together.

Here is a simple Groovy tool that counts the resolved issues in the passed collection:

resolvedCounter = new ResolvedCounterTool()

public class ResolvedCounterTool {
	public long countResolved(issues) {
		Closure query = { it.resolutionDate != null }
		return issues.findAll(query).size()

Now, bring this script to your favorite IDE, and add a beautifully simple test case method:

public void testCountResolvedTasks() {
	def issues = [ [:], [ resolutionDate: new Date() ], [:] ] as Set
	def result = countResolved(issues)
	assert result == 1

You can run the test trivially:

resolvedCounter = new ResolvedCounterTool()

When your tests are running fine, comment out the invocation of the tests, and deploy the script back to the server.

You may be curious, what happens when running tests in Jira? That's also doable, with one important remark.

Technically speaking, you could run tests before each export. Test failures will be written to the Jira log, and that's cool. For example, if you change the assert to this:

assert result == 2

you will see the following easy-to-read error in the Jira log:

Assertion failed:

assert result == 2
	|      |
	1      false

The problem is that failed tests will also make the export fail. (The assert will terminate the execution of the script, and the final PDF document cannot be rendered.) As the test failure details only appear in the Jira log, the users not looking at the log will only see a broken document with a cryptic error message.

Therefore, running Groovy tests in Jira is recommended only for development purposes.

Debugging scripts in Jira

You can efficiently develop simple scripts solely in the app's built-in editor, and use logging to understand its behaviour.

When things get more complicated, you can develop, test and debug scripts outside Jira, using the testing approach discussed above in your favorite IDE.

When deploying your work to production, logging should be your primary tool to diagnose problems. Additionally, this is also possible to debug the scripts within Jira in a rather tricky way! The idea is to launch Tomcat with JPDA debugging enabled, connect to Tomcat from your IDE (debugger) and open the script file in the IDE. The debugger will be intelligent enough to show you the script (that is being actually executed in the server) in the editor, and allow you to step over that!


  1. First start Jira with the remote debugging enabled, typically with this command:
    JIRA_HOME_DIR/bin/ jpda start
  2. Connect to the Jira with your remote debugger, as explained here.
  3. Open up the Groovy file in your IDE, and set a breakpoint in the code.
  4. Start an export which uses this Groovy script, and the debugger will stop at the breakpoint! (This is pretty crazy, right?)

Please note that for this to work, the Groovy file must be exactly the same in Jira and in your IDE, otherwise line number will not match.

Advanced examples

Viewing the example templates and scripts shipped with the app is the absolute best way to study implementations of real-life export types. Even if those are not perfectly matching to what you need, study them for inspiration and good practices!

If you are a purist (or don't have the time), you can actually do all your work with a simple text editor!

If you would like to enjoy syntax highlighting, code-completion and so, we recommend to use the Eclipse IDE (a great general purpose development environment) combined with the following apps:

  1. Velocity UI to edit Velocity templates (*.vm).
  2. Groovy-Eclipse to edit Groovy scripts (*.groovy).

Next step

Read the recipes to learn useful patterns and tricks with Better PDF Exporter.


Ask us any time.