It's not the same without you

Join the community to find out what other Atlassian users are discussing, debating and creating.

Atlassian Community Hero Image Collage

Generating a REST client for Jira Cloud

Atlassian makes a Java REST client library to work with Jira Server, but what if you want to write code to talk to Jira Cloud? The APIs are different enough between Jira Server and Jira Cloud that the Jira Server client library does not work with Jira Cloud.

In this post I’ll take you through the process of generating Java code from the Open API specification that Atlassian publishes (using swagger-codegen), and then writing a simple Java application that creates an issue in Jira Cloud.

Open API Specification

If you go to the documentation for the Jira Cloud REST API at https://developer.atlassian.com/cloud/jira/platform/rest/v3/ 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 link 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, for now we’ll save a copy locally.

Swagger and Generating Code

Swagger is a way of documenting APIs in a standard way. I won’t discuss that here, but you can read more at https://swagger.io/

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 at https://github.com/swagger-api/swagger-codegen

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 .

If you run that now, you’ll find that there are some things about the spec that swagger-codegen doesn’t like.

Making swagger-codegen happy

I’m going to edit the swagger-v3.v3.json file I downloaded to make it work better with swagger-codegen.

FieldValueClause

The first problem is that there are a series of valid operators you can use in JQL. When swagger-codegen writes an enum to represent those values it gets confused, as the values are things like “>” and “~”.

The operator attribute for FieldValueClause is a string type, which is fine. It is the enum that swagger-codegen doesn’t like. I’m going to edit the spec to say that the operator is simply a string.

I have added formatting for readability, but in this file there is no formatting. It’s one long line!

Edit swagger-v3.v3.json, and search for:

"FieldValueClause": { 
"required": [
"field","operand","operator"
]

Shortly after this you’ll see:

"operator": { 
"type": "string",
"description": "The operator between the field and operand.",
"enum": [
"=","!=",">","<",">=","<=","in","not in",null,"~=","is","is not"
]
}

If you remove the enum section so that this section looks like this:

"operator": { 
"type": "string",
"description": "The operator between the field and operand."
}

Don’t forget to remove the comma before “enum”.

Server URL

The other problem is that the swagger file has an empty string for the server URL. You’ll find that swagger-codegen doesn’t like this. You can put your server URL in there, or you can just put any URL in there and supply the server URL in your code. I’m going to do the latter.

Near the top of the file you will find this. (Again, I have added formatting here for readability.)

"servers": [ {
"url": ""
} ]

Just put a valid URL in there, like this:

"servers": [ {
"url": "https://mysite.atlassian.net"
} ]

Project Type

This problem won’t cause your code generation to fail, but it might cause the code not to compile later. The spec lists 4 values for project type, but practically there are only 2. You can fix it here, or you can just fix it in your generated code later if you find it doesn’t compile.

Again I have added formatting for clarity.

Find this section:

"enum": [
"CLASSIC",
"NEXTGEN",
"classic",
"next-gen"
]

Change it to read:

"enum": [
"classic",
"next-gen"
]

Configuring Code Generation Parameters

Now I’m almost ready. I didn’t want the default configuration, so I created a config.json for swagger-codegen. You’ll probably want to do the same, as you’ll want to customise the package names, and the Maven artifact/group names.

Create a file called config.json like this:

{
    "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"
}

You might want to change which client library it uses. Check the documentation for swagger-codegen. I’m using the “jersey2” flavour.

Some of the class and method names that are auto-generated are a bit difficult to deal with. You can customise swagger-codegen in many ways, but I won’t go into detail here.

Generating the code

Let’s run it. Now our swagger-codegen invocation looks like:


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

This generates a Maven 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.

A suggestion: Commit the generated code into your version control, along with your config.json, AND the command you used to run it. This way, in the future when the API changes, if you re-generated the code, it’s easy to see what changed.

Using the code

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

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.

    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.comAtlassianJiraRestV2IssueCurrentUserResourceGetCurrentUserGet(null);

        // Find the project called "GDPR"
        ProjectsApi projectsApi = new ProjectsApi(apiClient);
        PageBeanProject projects = projectsApi.comAtlassianJiraRestV2IssueProjectResourceSearchProjectsGet(
            null, // startAt
            null, // maxResults
            null, // orderBy
            "GDPR", // 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.comAtlassianJiraRestV2IssueIssueResourceCreateIssuePost(issueCreateParams, true);

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

 

2 comments

Useful to know. But I think I'll stick to Jira Python where I can. The length of method names in this Java code alone is off-putting!

Also, it would be very helpful if Atlassian published a list of differences between the Server and Cloud REST APIs

Ben Kelley Atlassian Team Aug 23, 2020

We fixed some of the problems I described above. See the follow-up blog post here: https://blog.developer.atlassian.com/update-to-jira-clouds-swagger-openapi-docs/

Comment

Log in or Sign up to comment
TAGS

Community Events

Connect with like-minded Atlassian users at free events near you!

Find an event

Connect with like-minded Atlassian users at free events near you!

Unfortunately there are no Community Events near you at the moment.

Host an event

You're one step closer to meeting fellow Atlassian users at your local event. Learn more about Community Events

Events near you