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

 

29 comments

Matt Doar
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
February 24, 2020

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
Atlassian Team members are employees working across the company in a wide variety of roles.
August 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
David Gerber January 26, 2021

Now the same for confluence please.

Like Benjamin C. John likes this
Neeraj March 15, 2021

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
Atlassian Team members are employees working across the company in a wide variety of roles.
April 5, 2021

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

Like # people like this
Andreas Schröck May 2, 2021

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
Atlassian Team members are employees working across the company in a wide variety of roles.
May 2, 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.

Andreas Schröck May 3, 2021

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

Ben Kelley
Atlassian Team
Atlassian Team members are employees working across the company in a wide variety of roles.
May 4, 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
Matteo De Franceschi May 5, 2021

Hello,

how can i access from swagger with Oauth authorization?

Ben Kelley
Atlassian Team
Atlassian Team members are employees working across the company in a wide variety of roles.
May 5, 2021

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
Matteo De Franceschi May 6, 2021

Thanks for your answer @Ben Kelley 

Eric Johansson May 15, 2021

@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
Atlassian Team members are employees working across the company in a wide variety of roles.
May 16, 2021

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

Benjamin C. John July 18, 2021

@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
Atlassian Team members are employees working across the company in a wide variety of roles.
July 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
Thomas C Buell August 12, 2021

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

Ben Kelley
Atlassian Team
Atlassian Team members are employees working across the company in a wide variety of roles.
August 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
Atlassian Team members are employees working across the company in a wide variety of roles.
August 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.

Bill Bill November 27, 2021

Stupid question here.  Instead of making us generate our own, why not do this and publish it in maven central?  Preferably with a page to describe how to use it?  Instead of a blog post and a follow-up article that took forever to unearth by googling?

I've been trying to figure out how to use com.atlassian.jira:jira-rest-api of various flavors, including versions that were published to maven-atlassian-external within the last few weeks, but there's no consistency, no source code to read, no documentation, and frankly the jar file looks very little like an actual api.

Sometimes I feel like Atlassian has turned a beautiful product into a company that hates developers :(

Like Steffen Opel _Utoolity_ likes this
Ben Kelley
Atlassian Team
Atlassian Team members are employees working across the company in a wide variety of roles.
December 6, 2021

Hey @Bill Bill . I guess one of the things about cloud is that there are small changes all the time. Any published client for cloud would be out of date almost immediately.

The jar file you are looking at is probably for Jira Server. For Jira Server, you installed a particular version of Jira on your system, and so you can use the companion jar file for that version (assuming it meets your requirements). You are unlikely to upgrade your version of Jira Server very often, but this doesn't really work for cloud.

A couple of years ago there were breaking changes to Jira Cloud APIs around GDPR. This surprised a few people who had bundled an old version of the Jira Server client, and didn't realise that Cloud APIs do change over time.

Generating a client library from an Open API spec is fairly straightforward. You could include the spec as part of your build, and then use the swagger-codegen Maven plugin to build it for you every time. (Note that this is an older article, and since then we made our published spec work much better with a standard toolchain.)

Practically you are probably going to generate your client once, and then keep using it until there is some new feature you want to support.

Bill Bill December 7, 2021

@Ben Kelley "I guess one of the things about cloud is that there are small changes all the time. Any published client for cloud would be out of date almost immediately."

This is why there are versioned jar modules published to maven repositories and indexed on mvnrepository.com.  It would be *trivial* for Atlassian to maintain this, to push new changes when ready, but instead you force developers outside of Atlassian to *guess* when changes happen and then manually maintain their client interface.

"The jar file you are looking at is probably for Jira Server."

It would be great if there were any kind of documentation anywhere to describe this.  Is that even the case?  I know Atlassian has (unfortunately) dropped support for Server, but the jars published out to that repo are new.

"didn't realise that Cloud APIs do change over time."

Sorry, but you're blaming the victim there.  If Atlassian took any time to communicate these things, there would be less surprise.

"Generating a client library from an Open API spec is fairly straightforward."

If you're a large organization, sure.  I'm a one-man shop, and once upon a time Atlassian understood that the strength of the community is in small organizations, not large ones with flunkies and people with cycles to spare.  It took me a couple of hours to get this to work, in a form that I was comfortable with (because now I have to manage the source, not just import a jar that was built by the people whose core competency it is).

I'm even more irritated because I just last night realized that I'm going to have to go through the same waste of time again today because I need a Jira Software client for another project I'm working on.

"Practically you are probably going to generate your client once, and then keep using it until there is some new feature you want to support."

Interestingly enough, that's a direct contradiction to what you wrote above.

I would be happy to help Atlassian set up a pipeline or whatever you need, to fix this situation.  At present, in my view, you're making the community do your work.

Like # people like this
Ben Kelley
Atlassian Team
Atlassian Team members are employees working across the company in a wide variety of roles.
December 7, 2021

The documentation for the specific version of Jira Server you have does talk about which major/minor versions the different client APIs support. I get that if you just grab the jar from a repo it doesn't link back to the docs describing what that jar is for.

I understand that if you only use one API, it would be convenient if you could just drop in a library that calls that API for you, and so I guess shipping the library rather than the machine readable spec would make your life easier.

The follow-up post to this one talks about changes we made so that you can just drop our spec into your generation tool of choice and get working code, so yes there is always room for improvement.

Keeping track of API changes is also difficult at the moment. While there are blogs that describe breaking changes (and the timeframe), it is currently hard to see smaller changes. We are currently looking at a tool that will describe all API changes (based on changes to the spec) in an automated fashion, which will make it easier to see what changed, and when.

David Smith January 14, 2022

Hi, @Ben Kelley Getting this error while using swagger generated, Could you please take a look and suggest any workaround for this? It would be a great help if you could help with this issue?


https://elmah.io/tools/stack-trace-formatter/80eb332a6f7a4fc7a65cb5c6a9ee7e1a

Basically, the discriminator being generated is empty, how to solve this?

return getClassByDiscriminator(
classByDiscriminatorValue,
getDiscriminatorValue(readElement, ""));

 

Ben Kelley
Atlassian Team
Atlassian Team members are employees working across the company in a wide variety of roles.
January 17, 2022

Comment

Log in or Sign up to comment
TAGS
AUG Leaders

Atlassian Community Events