How do I upload attachment to JIRA Issue via REST API?

Loïc GRIVEAU January 8, 2014

Is it possible to upload attachment in java using REST API without using cURL ?

Is it possible to upload a file using REST API BROWSER ?

11 answers

1 accepted

7 votes
Answer accepted
Loïc GRIVEAU January 9, 2014

Here my code for upload and download attachment :

public boolean addAttachmentToIssue(String issueKey, String fullfilename) throws IOException{

		CloseableHttpClient httpclient = HttpClients.createDefault();
		
		HttpPost httppost = new HttpPost(jira_attachment_baseURL+"/api/latest/issue/"+issueKey+"/attachments");
		httppost.setHeader("X-Atlassian-Token", "nocheck");
		httppost.setHeader("Authorization", "Basic "+jira_attachment_authentication);
		
		File fileToUpload = new File(fullfilename);
		FileBody fileBody = new FileBody(fileToUpload);
		
		HttpEntity entity = MultipartEntityBuilder.create()
				.addPart("file", fileBody)
				.build();
		
		httppost.setEntity(entity);
        String mess = "executing request " + httppost.getRequestLine();
        logger.info(mess);
        
        CloseableHttpResponse response;
		
        try {
			response = httpclient.execute(httppost);
		} finally {
			httpclient.close();
		}
        
		if(response.getStatusLine().getStatusCode() == 200)
			return true;
		else
			return false;

	}
	
	public boolean getAttachmentFromIssue(String contentURI, String fullfilename) throws IOException {
        
		CloseableHttpClient httpclient = HttpClients.createDefault();
        
        try {
            HttpGet httpget = new HttpGet(contentURI);
            httpget.setHeader("Authorization", "Basic "+jira_attachment_authentication);
                        
            System.out.println("executing request " + httpget.getURI());

            CloseableHttpResponse response = httpclient.execute(httpget);
            
            int status = response.getStatusLine().getStatusCode();
            if (status >=200 && status < 300) {
            	HttpEntity entity = response.getEntity();
            	if (entity.isStreaming()) {
            		byte data[] = EntityUtils.toByteArray(entity);
            		FileOutputStream fout = new FileOutputStream(new File(fullfilename));
            		fout.write(data);
            		fout.close();
            	}
            }
		} finally {
            httpclient.close();
        }
        
        return true;
	}

jira_attachment_authentication is define elsewhere as :

jira_attachment_authentication = new String(org.apache.commons.codec.binary.Base64.encodeBase64((user+":"+pass).getBytes()));

J D
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
November 5, 2014

Can you show me your which is getting the file? File fileToUpload = new File(fullfilename); FileBody fileBody = new FileBody(fileToUpload);

Yagnesh Bhat August 11, 2016

Loïc GRIVEAU, if I try to use the same code to move attachments from one JIRA instance to another, it gives 415 error. : 415 Unsupported Media Type.

 

Any idea?

Like # people like this
Murugavel Parthasarathy January 11, 2017

Hi, can you pls share the jar/lib that you used in your program?

Guillermo Meert February 22, 2019

you save my day, thanks!!

5 votes
Danut M _StonikByte_
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
February 15, 2019

I managed to make it work with PostMan.

Header must contain:

Content-Type: multipart/form-data

X-Atlassian-Token: no-check 

PM_Headers.jpg

Body must be of type "form-data" and must contain a key named "file" with value a specified file (I had to use the file picker, see the picture).

PM_Body.jpg

Ramandeep Singh Nagi July 14, 2020

Thanks, this worked for me.

parthiban_selvaraj October 19, 2020

Hi @Danut M _StonikByte_  , @Ramandeep Singh Nagi ,

 

I tried to attach the attachment via postman by using your above Info. When I used Content-Type as "multipart/form-data" am getting the below error.

error.PNG

But I removed Content-Type and my postman was successful.

success.PNG

 

Did I missed anything with "Content-Type" ?

 

Regards,

Parthiban

Ramandeep Singh Nagi November 1, 2020

Hello Parthiban,

 

I used the following settings in my Postman:

 

POST Url:

https://jira.your.url.com/rest/api/latest/issue/ABCD-1234/attachments

Headers:

Content-Type: multipart/form-data,

X-Atlassian-Token: no-check,

charset: UTF-8,

Accept-Encoding: gzip, deflate, br

Body -> form-data:

file: <give path to your file to be attached>

 

I hope this helps.

 

Regards,

Ramandeep Singh

Ana Dimitrioglo November 6, 2020

Thanks! Worked like a charm.

Chris Fortmueller March 29, 2021

@Ramandeep Singh Nagi Could you kindly post a screenshot of your solution?

2 votes
Brian Richardson July 19, 2017

This works for me:

 

  const auth = 'Basic ' + new Buffer(props['jiraAppUserName'] + ':' + props['jiraAppPassword']).toString('base64');
var options = {
url: 'https://JIRAUAT.xx.xx/rest/api/latest/issue/XEA-1255/attachments',
headers: {
'Authorization': auth,
'X-Atlassian-Token': 'nocheck'
}
};

var r = request.post(options, function (err, res, body) {
if (err) {
console.error(err);
resOut.status(500).json({
messages: 'outch',
obj: {}
});
} else {
console.log('Upload successful! Server responded with:', body);
resOut.status(200).json({
messages: 'successfully updated jira ticket',
obj: {}
});
}
}
);

var form = r.form();
form.append('file', fs.createReadStream('./somefile.png'));

 

jiracloud1 jira December 28, 2017

Can you please attach the complete file/code?

Like Ian Keller likes this
1 vote
Ivo May 21, 2019
HttpRequestFactory requestFactory = new NetHttpTransport().createRequestFactory(parameters);

MultipartContent content = new MultipartContent().setMediaType(
new HttpMediaType("multipart/form-data").setParameter("boundary", "__END_OF_PART__"));


for(Attachment attachment : attachments) {
File attachmentFile = ComponentAccessor.getAttachmentManager().
streamAttachmentContent(attachment, new FileInputStreamConsumer(attachment.getFilename()));

String mimeType = URLConnection.guessContentTypeFromName(attachmentFile.getName());

FileContent fileContent = new FileContent(mimeType, attachmentFile);
MultipartContent.Part part = new MultipartContent.Part(fileContent);
part.setHeaders(new HttpHeaders().set(
"Content-Disposition",
String.format("form-data; name=\"file\"; filename=\"%s\"", attachmentFile.getName())));
content.addPart(part);
}


HttpRequest request = requestFactory.buildPostRequest(jiraUrl, content);
request.getHeaders().set("X-Atlassian-Token", "no-check");
return request.execute();

Java code using google-http to POST attachments to /rest/api/2/issue/{issueIdOrKey}/attachments using OAuth. Still trying to figure out format for posting attachments on issue create.

Saurabh_Gupta July 18, 2019

What will be body for this endpoint i am using postman and it is not working for me : /rest/api/2/issue/{issueIdOrKey}/attachments

can anyone share postman body.

LostInMadness Forever December 2, 2019

neither for me.... i have try all :(

Dobrivoje Prtenjak March 24, 2020

Hi guys ! I've managed to POST attachment via Postman. An example :
https://jira-dev.server_url.com/rest/api/2/issue/NOC-618/attachments
Everything works fine.

However, I need to implement via Java service which use OAuth1.
First, there is a need to manipulate header, that is, to insert

"X-Atlassian-Token", "no-check"

and then, to upload actual input streams.

Any ideas ?

mcander March 24, 2020

My example has it shown 

request.getHeaders().set("X-Atlassian-Token", "no-check");

What is the problem? 

Dobrivoje Prtenjak March 24, 2020

Hi ! Thanks for a quick response !
Well, I've done authentication via OAuth, from a Jira tutorial, and have this method for post request, and i don't know how to manipulate header :

public HttpResponse executePrimitivePostRequest(@NonNull String url, @NonNull GenericData contentGenericData) {
String apiCallUrlPath = JIRA_HOME_URL + url;

try {
OAuthParameters parameters = jiraOAuthClient.getParameters( JIRA_ACCESS_TOKEN, JIRA_SECRET_KEY, JIRA_CONSUMER_KEY, JIRA_PRIVATE_KEY );
HttpContent content = new JsonHttpContent( new JacksonFactory(), contentGenericData );
HttpResponse response = postResponseFromUrl( parameters, new GenericUrl( apiCallUrlPath ), content );

return response;
} catch (HttpResponseException hre) {
String errMsg = "Executing Post Request Error. " + hre;
LOGGER.log( Level.SEVERE, errMsg, hre );
throw new RuntimeException( errMsg, hre );
} catch (Exception e) {
String errMsg = "Executing Get Request, no result.";
LOGGER.log( Level.INFO, errMsg, e );
throw new RuntimeException( errMsg, e );
}
}
1 vote
Aleksander Mierzwicki
Atlassian Team
Atlassian Team members are employees working across the company in a wide variety of roles.
January 8, 2014

Yes it's possible. For example JIRA REST Java Client does that: https://bitbucket.org/atlassian/jira-rest-java-client/src/bd758b52f53978033bdd0d8071edd75d7bdcc607/core/src/main/java/com/atlassian/jira/rest/client/internal/async/AsynchronousIssueRestClient.java?at=master#

You'll have to send multipart request - the code will depend on HTTP client that you use.

There is already answer for that question here: http://stackoverflow.com/questions/18631361/add-attachment-to-jira-via-rest-api

Loïc GRIVEAU January 8, 2014

I copy paste the answer from the stackoverflow.comlink, but some dependencies are missing (MultipartEntity, HttpEntity, ...)

Can you help me ?

RambanamP
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
January 9, 2014

you can download dependencies from here

http://hc.apache.org/downloads.cgi

RambanamP
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
January 9, 2014

@Loïc GRIVEAU, are you able to attach to issues using sample in stackoverflow link?

if yes then can you sahre your code here?

J D
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
November 5, 2014

+1

Yagnesh Bhat
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
August 11, 2016

@Aleksander Mierzwicki, is there a way we can copy ALL attachments from one JIRA issue in one server to another JIRA issue in another server? I have been trying all the code snippets given for this question and am getting 415 Unsupported media type error.

0 votes
Dobrivoje Prtenjak March 30, 2021

I've done the other way, by pure "spring-like" flow so to speak.
Actually, I have a complete git project, however, for the attachment, I still need to add functionality, however, I'll give you fast and dirty solution until I add in project.

For details please visit : https://github.com/dobrivoje/jiraintegration


Here is :

REST :
..

@POST
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces(MediaType.APPLICATION_JSON)
@Path("createIssueWithAttachment")
ResponseEntity<ResponseIssueCreationDto> createIssueWithAttachmentStreamForSupport(
@FormDataParam("summary") String summary,
@FormDataParam("nmeTeam") @NonNull String nmeTeam,
@FormDataParam("handOverDate") @NonNull Date handOverDate,
@FormDataParam("supportType") @NonNull String supportType,
@FormDataParam("description") String description,
@FormDataParam("siteAddress") String siteAddress,
@FormDataParam("siteContacts") String siteContacts,
@FormDataParam("topology") String topology,

@FormDataParam("file") InputStream attachment,
@FormDataParam("file") FormDataContentDisposition fileDetail,
@FormDataParam("file") FormDataBodyPart body);

...

 

REST implementation :

@Override
public ResponseEntity<ResponseIssueCreationDto> createIssueWithAttachmentStreamForSupport(
String summary, String nmeTeam,
Date handOverDate, String supportType, String description,
String siteAddress, String siteContacts, String topology,
InputStream attachment,
FormDataContentDisposition fileDetail,
FormDataBodyPart body) {

return ResponseEntity.status( HttpStatus.OK )
.contentType( APPLICATION_JSON )
.body(
service.createNocIssueWithStreamAttachmentForSupport(
supportType, nmeTeam, handOverDate, siteAddress,
siteContacts, topology, description,
summary, attachment, fileDetail, body ) );
}

 

Custom service (interface) :

ResponseIssueCreationDto createIssueWithStreamAttachmentForSupport(@NonNull String supportType, @NonNull String nmeTeam,
Date handOverDate, String siteAddress,
String siteContacts, String topology,
String description, String summary,
InputStream attachment,
FormDataContentDisposition fileDetail,
FormDataBodyPart body);


Custom service implementation :

@Override
public ResponseIssueCreationDto createIssueWithStreamAttachmentForSupport(String supportType, String nmeTeam,
Date handOverDate, String siteAddress,
String siteContacts, String topology,
String description, String summary,
InputStream attachment,
FormDataContentDisposition fileDetail,
FormDataBodyPart body) {

ResponseIssueCreationDto newIssue = createBasicIssueWithoutAttachmentForSupport(
supportType, nmeTeam, handOverDate, siteAddress, siteContacts, topology, description, summary );

addAttachmentToIssue( attachment, fileDetail, body, newNocIssue.getIssueKey() );

return newNocIssue;
}

 

@Override
public ResponseIssueCreationDto createBasicIssueWithoutAttachmentForSupport(
String supportType, String nmeTeam, Date handOverDate, String siteAddress,
String siteContacts, String topology, String description, String summary) {

//<editor-fold desc="Stylized error message, for only missing mandatory fields">
boolean isErrSupportType = supportType == null;
boolean isErrNmeTeam = !NocNMETeam.isValidTeam( nmeTeam );
boolean isErrHandOverDate = handOverDate == null;
String errNew = (isErrSupportType ? "Support type, " : "")
+ (isErrNmeTeam ? "NME team, " : "")
+ (isErrHandOverDate ? "Handover date " : "");
String errorMessage = errNew.substring( 0, ((isErrSupportType || isErrNmeTeam) && !isErrHandOverDate) ?
errNew.length() - 2 : errNew.length() ).concat( " must be defined." );
//</editor-fold>

Commons.ensureNonNullsWithMessageFor( errorMessage, supportType, nmeTeam, handOverDate );

//<editor-fold desc="Service desk id, and Request type id">
ServiceDesksDto serviceDeskProjectDto = getAllServiceDesks().orElseThrow( () -> new JiraAllServiceDesksException( errorMessage ) );

ServiceDeskProjectDto sdp = serviceDeskProjectDto
.getValues()
.stream()
.filter( p -> JiraNocProjectKey.equalsIgnoreCase( p.getProjectKey() ) )
.findFirst()
.orElseThrow( () -> new JiraServiceDeskProjectException( errorMessage ) );
String projectIdStr = Optional.ofNullable( sdp.getId() ).orElseThrow( RuntimeException::new );

ServiceDesksRequestTypeDto serviceDesksReqTypeDto = getServiceDeskWithRequestTypeForDesk( Long.valueOf( projectIdStr ) )
.orElseThrow( () -> new JiraServiceDeskRequestTypeException( errorMessage ) );
ServiceDeskRequestTypeValueItemDto srvDesksReqTypeValDto = serviceDesksReqTypeDto
.getValues()
.stream()
.filter( f -> projectIdStr.equals( f.getServiceDeskId() ) )
.filter( n -> JiraNocSupport.equalsIgnoreCase( n.getName() ) )
.findFirst()
.orElseThrow( () -> new JiraServiceDeskRequestTypeValueException( errorMessage ) );
//</editor-fold>

GenericData rootNodeResult = new GenericData();
rootNodeResult.put( ServiceDeskId, projectIdStr );
rootNodeResult.put( RequestTypeId, srvDesksReqTypeValDto.getId() );

GenericData childrenRequestFieldValues = new GenericData();

Date hoDate3BDays = DateUtil.getEndBusinessDayFor( handOverDate, 3 );
String hoDate = new SimpleDateFormat( JIRA_MISTERY_DATETIME_PATTERN2 ).format( hoDate3BDays );
childrenRequestFieldValues.put( REQIRED_BY_DATE, hoDate );

childrenRequestFieldValues.put( SITE_ADDRESS, Commons.getFieldWithValidLengthOf255( siteAddress ) );
childrenRequestFieldValues.put( SITE_CONTACTS, siteContacts );
childrenRequestFieldValues.put( TOPOLOGY, topology );
childrenRequestFieldValues.put( SUMMARY, summary );
childrenRequestFieldValues.put( DESCRIPTION, description );

GenericData childReqNMETeam = new GenericData();
try {
childReqNMETeam.put( "value", NocNMETeam.getValidCode( nmeTeam ) );
childrenRequestFieldValues.put( NME_TEAM, childReqNMETeam );
} catch (UnsupportedOperationException nte) {
throw new JiraNOCTeamException( NocNMETeam.getValidTeams(), JIRA_HOME_URL, JiraNocReqCustPortalId, JiraNocReqCustomerPortalCreateId );
}

GenericData childReqSupportType = new GenericData();

try {
childReqSupportType.put( "value", NocSupportType.getValidCodeFor( supportType ) );
childrenRequestFieldValues.put( SUPPORT_TYPE, childReqSupportType );
} catch (UnsupportedOperationException nst) {
throw new JiraNOCSupportTypeException( NocSupportType.getValidTypes(), JIRA_HOME_URL, JiraNocReqCustPortalId, JiraNocReqCustomerPortalCreateId );
}

rootNodeResult.put( RequestFieldValues, childrenRequestFieldValues );

return jiraClient.executeBasicPost( ResponseIssueCreationDto.class, apiCallIssueCreateByRequestType, rootNodeResult, true );
}

 

This is what you actually need :

@Override
public List<ResponseItemAttachmentDto> addAttachmentToIssue(InputStream inputStream, FormDataContentDisposition fileDetail,
FormDataBodyPart body, @NonNull String issueKey) {

String urlParamCall = MessageFormat.format( jiraAddAttachmentToIssueKey, issueKey );

GenericData rootNodeResult = new GenericData();
rootNodeResult.put( ATTACHMENT, new AttachmentInfoHolderDto( inputStream, fileDetail, body ) );

GenericData headers = new GenericData();
headers.put( "X-Atlassian-Token", "no-check" );

return jiraClient.executePostExpectingList( ResponseItemAttachmentDto.class, urlParamCall,
headers, rootNodeResult, true );
}
0 votes
CMC Consulting September 28, 2020

Hello, just facing problem with UTF-8 characters in file name.
When uploading with postman, like in example everything is fine, but when uploading by java API, german umalut characters are converted as ???? 
Any suggestion?

0 votes
Dan Dickinson May 14, 2020

Here's a working method from a Python 3.x module. The key things that aren't spelled out in many solutions:

  • all file data must be read as binary data, even plain text files
  • the multipart boundary string need not be anything fancy, but it must be a string that is unlikely to occur in the file data
  • multipart lines are separated by Carriage Return + Line Feed
  • all the multipart strings must be converted to binary, then wrapped around the binary file contents
  • when calculating "Content-Length", take the length of the entire multipart form, not just the length of the file contents
def add_attachment(self, path):
    '''Upload a file attachment from a local file path.
    '''
    error_messages = []
    try:
        f = open(path, 'rb'# all files, even plain text, must be read as binary data
        file_contents = f.read()
        f.close()
    except:
        error_messages.append('cannot read ' + path)

    if not error_messages:
        # create multipart/form-data as defined in RFC 1867 (see: https://www.ietf.org/rfc/rfc1867.txt)
        boundary = binascii.hexlify(os.urandom(16)).decode('ascii')
        separator = '\r\n' # CRLF is the standard separator in multipart/form-data
        # multipart/form-data consists of: prefix + file contents + suffix
        prefix = '--{}{}'.format(boundary, separator)
        prefix += 'Content-Disposition: form-data; name="file"; filename="{}"{}'.format(os.path.basename(path), separator)
        prefix += 'Content-Type: application/octet-stream{}'.format(separator)
        prefix += separator
        suffix = separator + '--{}--{}'.format(boundary, separator)

        # convert the prefix and suffix text to binary, and then join them around the binary file contents
        prefix = prefix.encode('utf-8')
        suffix = suffix.encode('utf-8')
        data = b''.join([prefix, file_contents, suffix])

        headers = {}
        headers['Content-Type'] = 'multipart/form-data; boundary={}'.format(boundary)
        headers['X-Atlassian-Token'] = 'no-check'
        headers['Content-Length'] = str(len(data)) # length must include the entire multipart/form-data, not just the file contents

        api = 'api/2/issue/' + self.key + '/attachments'
        (status, response) = self._session._call_api(api, headers=headers, data=data)
        if not (status >= 200 and status <= 299):
            error_messages += standard_errors(status, response)

    return error_messages

 

Lily Tran July 13, 2020

Hello;

Do you have sample code for downloading all attachments of all Jira issues reported in a Jira project? 

Thank you;

Lily

0 votes
Seema Itagi January 8, 2019

HttpPost postRequest = new HttpPost("URL+Post REST API");

does this mean like this

HttpPost postRequest = new HttpPost("https:/abc.atlassian.net/rest/api/2/issue+https://abc.atlassian.net/browse/MSAM-42?filter=-4");

0 votes
Sagar Kalburgi August 5, 2015

THIS CODE WORKS:

 

public class JiraRest {

public static void main(String[] args) throws ClientProtocolException, IOException
{
String pathname= "<Full path name of the attachment file>";
File fileUpload = new File(pathname);

HttpClient httpClient = HttpClientBuilder.create().build();
HttpPost postRequest = new HttpPost("URL+Post REST API");
BASE64Encoder base=new BASE64Encoder();
String encoding = base.encode ("username:password".getBytes());
postRequest.setHeader("Authorization", "Basic " + encoding);
postRequest.setHeader("X-Atlassian-Token","nocheck");

MultipartEntityBuilder entity=MultipartEntityBuilder.create();
entity.addPart("file", new FileBody(fileUpload));
postRequest.setEntity( entity.build());
HttpResponse response = httpClient.execute(postRequest);
}
}


Required JARs:

All JARs in lib folder of httpcomponents-client-4.5-bin and sun.misc.BASE64Decoder.jar

Test October 27, 2018

Awesome. This worked perfectly..

Natarajan Senthil Kumar November 21, 2018

Where is the ticket URL

?

Sagar Kalburgi November 22, 2018

Sorry I posted this answer more than 3 years back, I don't remember anything as of now 

0 votes
RambanamP
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
January 9, 2014

if you want to do it through perl then try with this

https://metacpan.org/pod/JIRA::Client::Automated#attach_file_to_issue

Suggest an answer

Log in or Sign up to answer