Getting started with JIRA REST API

JIRA allows programmatic access to data through it's Java based REST API. Essentially every call to the Java API translates to an HTTP request to the JIRA REST Services.

The JIRA REST API uses JSON as its communication format, and the standard HTTP methods like GET, PUT, POST and DELETE URIs for JIRA's REST API resource have the following structure:

http://host:port/context/rest/api-name/api-version/resource-name

The current API version is 2. However, there is also a symbolic version, called latest, which resolves to the latest version supported by the given JIRA instance.

As an example, if you wanted to retrieve the JSON representation of issue JRA-9 from Atlassian's public issue tracker, you would access:

https://jira.atlassian.com/rest/api/latest/issue/JRA-9

There is a WADL document that contains the documentation for each resource in the JIRA REST API. It is available at

https://docs.atlassian.com/DAC/rest/jira/jira-rest-plugin.wadl

To use the JIRA REST API, you need the following dependencies. The example here assumes you use Maven but you can use Gradle or Ivy or any other build tool as well

    <dependency>
        <groupId>com.atlassian.jira</groupId>
        <artifactId>jira-rest-java-client</artifactId>
        <version>2.0.0-m2</version>
    </dependency>
    <dependency>
        <groupId>com.atlassian.fugue</groupId>
        <artifactId>fugue</artifactId>
        <version>2.6.1</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-jdk14</artifactId>
        <version>1.7.25</version>
    </dependency>

You might need to add Atlassian public repository to your settings.xml file (if you are using Maven):

add this repository to your settings.xml

<repository>
    <id>atlassian-public</id>
    <url>https://m2proxy.atlassian.com/repository/public</url>
    <snapshots>
        <enabled>true</enabled>
        <updatePolicy>daily</updatePolicy>
        <checksumPolicy>warn</checksumPolicy>
    </snapshots>
    <releases>
        <enabled>true</enabled>
        <checksumPolicy>warn</checksumPolicy>
    </releases>
</repository>

and also this plugin repository:

<pluginRepository>
    <id>atlassian-public</id>
    <url>https://m2proxy.atlassian.com/repository/public</url>
    <releases>
        <enabled>true</enabled>
        <checksumPolicy>warn</checksumPolicy>
    </releases>
    <snapshots>
        <checksumPolicy>warn</checksumPolicy>
    </snapshots>
</pluginRepository>

To connect to a JIRA instance we need the following information:
*URL

*username

*password

We can use these info to create a JiraRestClient instance :

public JiraRestClient createClient(String jiraServerURL, String userName, String password) throws Exception {        
    URI jiraServerUri = new URI(jiraServerURL);    
    AsynchronousJiraRestClientFactory factory = new AsynchronousJiraRestClientFactory();
    final JiraRestClient restClient = factory.createWithBasicHttpAuthentication(jiraServerUri, userName, password);
    return restClient;
}

The JIRA REST Client is the main interface through which we’ll communicate with the Jira REST API.

Here, we’re using the basic authentication to communicate with the API. However, more sophisticated authentication mechanisms like OAuth are also supported

Searching for JIRA Issues

Every JIRA issue is represented by an instance of class Issue (

com.atlassian.jira.rest.client.domain.Issue )

JIRARestClient provides us API to fetch details for any JIRA Issue based on the issue key

private Issue getIssue(JiraRestClient restClient, String issueKey) {
    return restClient.getIssueClient().getIssue(issueKey).claim();
}

restClient.getIssueClient() returns an instance of IssueClient which is abstraction over JIRARestClient to search for Issues in the JIRA instance. Note that IssueClient.getIssue(String key) returns a Promise and not an Issue. A Promise is a placeholder for an asynchronous computation (very similar to Future). The underlying computation is not executed until you invoke claim() on the Promise.

What if you want to search for matching issues based on a criteria ?

You can use JQL (Jira Query Language) expressions to search for issues. Here is an example of a JQL expression :

priority in (Blocker, Critical) AND project in (ProjA, ProjB, ProjC)

You can find JQL specifications and documentation at :

https://confluence.atlassian.com/jiracore/blog/2015/07/search-jira-like-a-boss-with-jql

Here's a sample code for fetching Issues using JQL

SearchResult result = client.getSearchClient().searchJql(query).claim();

Note that searchJQL by default limits responses to at most 50 entries. So its better to use paginated queries like this :

    int startAt = 0;
    final int fetchSize = 1000;
    int fetchedInThisBatch= 0;

     do{
        fetchedInThisBatch = 0;
        SearchResult result =restClient.getSearchClient().searchJql(query, fetchSize, startAt).claim();
        Iterable<BasicIssue> issueIterable = result.getIssues();
        for (BasicIssue basicIssue : issueIterable ) {
            fetchedInThisBatch ++;
            // business logic here
        }
        startAt = startAt +fetchedInThisBatch;

    }while (fetchedInThisBatch == fetchSize);

JQL Search returns Iterable. To get Full details of an Issue, use the IssueClient with basicIssue.getKey() as argument.

Promise<Issue> issuePromise =restClient.getIssueClient().getIssue(basicIssue.getKey());
Issue issue = issuePromise.claim();

By Default, IssueClient doesnt include the change history as part of the response. To include change history, use :

Promise<Issue> issuePromise =restClient.getIssueClient().getIssue(basicIssue.getKey(),Arrays.asList(IssueRestClient.Expandos.CHANGELOG));
Issue issue = issuePromise.claim();

The change logs can be accessed by using the following API:

Iterable<ChangelogGroup> changeLogs = issue.getChangelog()

Here's an example where we extract the date when the Issue was marked as Fixed by iterating through the change logs :

private DateTime getResolutionDate(Iterable<ChangelogGroup> changeLogs) {
    return getEventDateTime(changeLogs, FieldType.JIRA, "resolution", "Fixed");
}

private DateTime getEventDateTime(Iterable<ChangelogGroup> changeLogs, FieldType fieldType, String fieldName, String fieldValue) {
    DateTime eventTime = null;
    for (ChangelogGroup group : changeLogs) {
        Iterable<ChangelogItem> changeLogItems = group.getItems();
        for (ChangelogItem item : changeLogItems) {
            if ( (fieldType == item.getFieldType()) && (fieldName.equals(item.getField())) && (fieldValue == null || fieldValue.equals(item.getToString()))) {
                eventTime = getEventDateTime(group);
                break;
            }
        }
    }
    return eventTime;        
}

Note the FieldType option. Because resolution is a built-in field in JIRA, we have used FieldType.JIRA as argument. For non standard or custom fields use FieldType.CUSTOM

If you are using JIRA/JIRA Extensions for Time Tracking, you can use JIRA API to extract the worklogs and perform statistical analysis on it

The following code extracts all worklogs from a JIRA issue

private void getWorklogs(Issue issue) {
    Iterable<Worklog> worklogIterable = issue.getWorklogs();
    for (Worklog worklog : worklogIterable) {
        System.out.println(worklog.getAuthor().getName())
        Double hours = worklogMap.get(role);
        if (hours == null) {
            hours = 0.0;
        }
        hours += worklog.getMinutesSpent()/60.0;
        System.out.println(hours);
    }
}

The JIRA REST API is not just aimed at read only operations. You can use the API to programmatically create a new JIRA as well :

 IssueInput input = new IssueInput(fields);
 Promise<BasicIssue> basicIssue = restClient.getIssueClient().createIssue(input);
 BasicIssue issue = basicIssue.claim();

To create a new JIRA Issue we need to provide an IssueInput instance to the JIRA Issue client. The IssueInput class wraps the JIRA Issue fields into a Map structure. To create an IssueInput instance, we need to pass a Map instance where keys are field names and values are field values

Alternately, you can also use the IssueInputBuilder to create an IssueInput instance :

IssueInput input = new IssueInputBuilder()
                      .setDescription(newDescription)
                      .build();

We can also programmatically add a comment to an existing JIRA issue :

public void addComment(Issue issue, String commentBody) {
    restClient.getIssueClient().addComment(issue.getCommentsUri(), Comment.valueOf(commentBody));
}

Or fetch all comments on an issue

public List<Comment> getAllComments(String issueKey) {
    Promise<Issue> issuePromise =restClient.getIssueClient().getIssue(issueKey);
    Issue issue = issuePromise.claim();
    return StreamSupport.stream(issue.getComments().spliterator(), false).collect(Collectors.toList());
}

comments powered by Disqus