Create
cancel
Showing results for 
Search instead for 
Did you mean: 
Sign up Log in

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());
        }
    }

 

19 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/

Like Steffen Opel _Utoolity_ likes this

Now the same for confluence please.

Like Benjamin C_ John likes this

Hi Ben,

Thanks of this wonderful article.

I was able to create a client using swagger-codegen with resttemplate as a library.

However, there are some minor issues which I came across:

  1. The class IssueTypeIds get created by the name IssueTypeIDs.java. This generates a compilation error and the file is required to be renamed. Not sure if this can be rectified in json.
  2. The model class name Component clashes with Spring annotation class Component. To handle this, I had to rename the model class Component to ProjectComponent.

Once fixed, I was able to generate a jar and write a sample client program over it.

Please see if the above issues can be resolved from swagger-v3.json itself.


Thanks,
--Neeraj

Like t.dietmaier likes this
Ben Kelley Atlassian Team Apr 05, 2021

Hi Neeraj. The IssueTypeIDs/IssueTypeIds problem should be resolved now.

Like # people like this

Hi Ben,

I have been using your example half a year ago and it was working back then. 

Now I have restarted my work and I tried to run it again.

This time I get an authentication error.

Did this change in the meanwhile? 

Error message: "Client must be authenticated to access this resource."

Ben Kelley Atlassian Team May 02, 2021

Hi @Andreas Schröck . Maybe it's worth asking about that in a new thread/question. Things like which resource you're accessing (e.g. is it for Jira Software or Jira Service Management/JSD?) and what HTTP client and language you're using might help to understand what's happening.

I have fixed my issue! It was a stupid typo - sorry for disturbing you!

Ben Kelley Atlassian Team May 04, 2021

Hi @Neeraj . I was working with Spring's REST template today, and I saw the issue you mentioned about Component. I have renamed Component to ProjectComponent as you suggested. This should be reflected in our spec in the next couple of days. Thanks for your feedback!

Like Steffen Opel _Utoolity_ likes this

Hello,

how can i access from swagger with Oauth authorization?

Hi @Matteo De Franceschi . It might be better to ask that in a new thread, but for Connect where you have AtlassianHostRestClients, and you are using the resttemplate flavour from swagger-codegen, you can construct an ApiClient from a RestTemplate, so like:

String baseUrl = "http://mysite.atlassian.net/";
AtlassianHostRestClients atlassianHostRestClients;
ApiClient apiClient = new ApiClient(atlassianHostRestClients.authenticatedAsHostActor());
apiClient.setBasePath(baseUrl);
ProjectsApi projectsApi = new ProjectsApi(apiClient);

If you are doing it some other way, maybe create a new question.

Like # people like this

@Ben Kelley it would be awesome if you could modify the swagger spec to be compatible with kotlin-client. 

Also, some of the operationIds seem to be break code generation.

Search for:

operationId": \w+\.

Example:

"operationId": "AddonPropertiesResource.getAddonProperties_get"
Ben Kelley Atlassian Team May 16, 2021

@Eric Johansson could you perhaps give some more details in a new thread/question?

@Ben Kelley , once again I would like to ask about Confluence, maybe you can trigger the responsible in the confluence Team. The API can be generated but there is the same issue with the operationId that generates toooooo long method names. Ignoring it is possible but then the method names are not clear.

Thank you in advance,

Benjamin

Ben Kelley Atlassian Team Jul 22, 2021

Hi @Benjamin C_ John . I did raise this with the Confluence team. They are taking a look at it.

Like Benjamin C_ John likes this

Thanks. Are there any maven dependencies needed for the Java code?

Ben Kelley Atlassian Team Aug 12, 2021

Hi @Thomas C Buell . Make sure you read my follow up post where some of the problems I mention here are fixed (my first comment on this page).

Yes there are Maven dependencies. They are slightly different depending on which "library" option you choose for Java. By default the Java generation creates a self contained library (with a pom.xml) which will specify the dependencies you need.

Ben Kelley Atlassian Team Aug 15, 2021

Hi @Benjamin C_ John . Check out the Confluence Open API spec. I made some fixes. I'll publish a separate blog post about it.

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