In a previous Atlassian Community article, I talked about some things that might block you in generating client code automatically from our published spec, and then using that generated code to call Jira.

Well, we removed those blockers! Also, we heard your feedback that you didn’t like the long and confusing method names.

So here’s a quick recap on what Swagger/OpenAPI is and how you might use it to generate client code to call Jira, and some suggestions on best practices in integrating with a cloud service.

I will also discuss our ongoing QA process to ensure that the spec is always usable.

Using our Open API spec

If you go to the documentation for the Jira Cloud REST API, you’ll see the “…” in the top right. Click that, and you’ll see the link for “Download OpenAPI Spec.” This is a JSON file that describes the REST API for Jira Cloud.

The actual URL to this file is https://developer.atlassian.com/cloud/jira/platform/swagger-v3.v3.json. Although swagger-codegen can generate APIs directly from a URL, I recommend saving a local copy of this file. I am going to use a local copy in my examples.

In this post I will deal with APIs common to all Jira project types, but the process is similar for Jira Software’s APIs, and Jira Service Desk’s APIs.

Swagger and Generating Code

While you are welcome to write code by hand to call Jira’s REST APIs, there are a lot of APIs, and new ones are being added all the time. Keeping that code up-to-date by hand would be time consuming.

Swagger is a way of documenting APIs in a standard way. I won’t discuss that in detail here. You can read more at https://swagger.io/ – this is also called Open API.

I am going to use the swagger-codegen tool to generate my client code. You can build this from source, but I just installed a version on my Mac using “brew install swagger-codegen.” (Read more here) There are also integrations for swagger-codegen that work with popular build tools, like Maven.

In theory this is the minimum command you’d need to generate some source code in the current directory from the spec:

swagger-codegen generate 
    -i https://developer.atlassian.com/.../swagger-v3.v3.json 
    -l java 
    -o .

That will generate you a usable client project in the current directory. If you read my previous post on this, you will remember that there were a number of other manual steps at this point. Not any more.

In theory you’re good to go, but I am going to recommend a few more things as best practice.

Configuring Code Generation Parameters

You will want to do things like choose package names and an HTTP client style that suits your project better. Create a config.json file for swagger-codegen.

For example:

{
    "modelPackage": "com.atlassian.jira.rest.client.model",
    "apiPackage": "com.atlassian.jira.rest.client.api",
    "invokerPackage": "com.atlassian.jira.rest.client",
    "library": "jersey2",
    "groupId": "com.atlassian.jira.rest",
    "artifactId": "client"
}

I’m using the “jersey2” flavour of HTTP client. There are different options depending on the language you used. Check the documentation for swagger-codegen.

To generate a client using a config file, use a command like:

swagger-codegen generate 
    -i ./swagger-v3.v3.json 
    -l java 
    -o ~/src/jira-client
    -c ./config.json

Configure swagger-codegen until you are happy with the code it generates.

Generating the code

The above command generates a Maven/Gradle project under ~/src/jira-client. You will want to customise the pom.xml a bit so suit your development practices, and how you build things that are going to use this code.

Check into version control

While the client code is generated, my recommended best practice is to version control the generated client code. Why? This helps you to know what changed since the last time you generated it.

Using the code

To test, I opened up the generated project directly in my IDE, and created a Main class.

My recommended best practice is to build the generated client as its own library. That way you can refresh the client periodically in a self contained way. Specify this library as a dependency to your projects that need to call Jira.

In this example I’m going to look up my user, find a project, find the bug issue type for that project, and create an issue.

If you have used our Open API spec before, you will notice how the method names are nice and readable now.

public static void main(String[] args) throws ApiException {
    ApiClient apiClient = new ApiClient();
    apiClient.setUsername("myemail@mycompany.com");
    apiClient.setPassword("abcde My app token abcde");
    apiClient.setDebugging(true);
    apiClient.setBasePath("https://myjira.atlassian.net");

    // Look up my user
    MyselfApi myselfApi = new MyselfApi(apiClient);
    User user = myselfApi.getCurrentUser(null);

    // Find the project called "BUGS"
    ProjectsApi projectsApi = new ProjectsApi(apiClient);
    PageBeanProject projects = projectsApi.searchProjects(
        null, // startAt
        null, // maxResults
        null, // orderBy
        "BUGS", // query
        null,  // typeKey
        null,  // categoryId
        null,  // searchBy
        null, // action
        "issueTypes", // expand issue types
        null  // status
        );
            
    if (projects.getValues().size() > 0) {
        // Find the first project
        Project project = projects.getValues().get(0);

        String issueTypeId = "0";
        // Find the bug issue type
        for (IssueTypeDetails issueTypeDetails : project.getIssueTypes()) {
            if (issueTypeDetails.getName().equalsIgnoreCase("bug")) {
                issueTypeId = issueTypeDetails.getId();
            }
        }

        // Create an issue
        IssuesApi issuesApi = new IssuesApi(apiClient);
        Map<String, Object> issueCreateParams = new HashMap<>();

        Map<String, Object> fields = new HashMap<>();
        fields.put("summary", "Create a test issue");
        Map<String, Object> issueTypeField = new HashMap<>();
        issueTypeField.put("id", issueTypeId);
        fields.put("issuetype", issueTypeField);
        Map<String, Object> projectField = new HashMap<>();
        projectField.put("id", project.getId());
        fields.put("project", projectField);
        // Assign to me
        Map<String, Object> assigneeField = new HashMap<>();
        assigneeField.put("accountId", user.getAccountId());
        fields.put("assignee", assigneeField);

        issueCreateParams.put("fields", fields);

        CreatedIssue createdIssue = issuesApi.createIssue(issueCreateParams, true);

        System.out.println("Created an issue, and assigned it to " + user.getDisplayName());
        System.out.println("The issue key is " + createdIssue.getKey());
    }
}

Continuous Quality

We know you depend on your Jira integrations continuing to work, and you would rather spend your time adding value to your organisation than figuring out how to integrate with Jira.

For that reason we have put in place a continuous integration strategy that basically does all the things I described above.

We regularly generate a client library, and then run tests using that generated code to make sure it does what it says on the box.

Update to Jira Cloud’s Swagger/OpenAPI docs