theonedev/onedev · error · ExplicitException

Error downloading attachment (url: %s, error message: %s)

Error message

Error downloading attachment (url: %s, error message: %s)

What it means

While importing issue attachments, consume() downloads each attachment's content URL with the JAX-RS client and validates the HTTP response via JerseyUtils.checkStatus(). If the response status is not successful, checkStatus returns an error message and the import throws this ExplicitException embedding both the URL and the message, aborting the import.

Source

Thrown at server-plugin/server-plugin-import-jiracloud/src/main/java/io/onedev/server/plugin/imports/jiracloud/ImportServer.java:807

						if (!dryRun) { 
							List<String> attachments = new ArrayList<>();
							
							if (fieldsNode.hasNonNull("attachment")) {
								long maxUploadFileSize = OneDev.getInstance(SettingService.class)
										.getPerformanceSetting().getMaxUploadFileSize()*1L*1024*1024; 
								for (JsonNode attachmentNode: fieldsNode.get("attachment")) {
									String attachmentName = attachmentNode.get("filename").asText();
									int attachmentSize = attachmentNode.get("size").asInt();
									if (attachmentSize >  maxUploadFileSize) {
										tooLargeAttachments.add(issueKey + ":" + attachmentName);
									} else {
										String endpoint = attachmentNode.get("content").asText();
										WebTarget target = client.target(endpoint);
										Invocation.Builder builder =  target.request();
										try (Response response = builder.get()) {
											String errorMessage = JerseyUtils.checkStatus(endpoint, response);
											if (errorMessage != null) { 
												throw new ExplicitException(String.format(
														"Error downloading attachment (url: %s, error message: %s)", 
														endpoint, errorMessage));
											}
											try (InputStream is = response.readEntity(InputStream.class)) {
												AttachmentService attachmentService = OneDev.getInstance(AttachmentService.class);
												String oneDevAttachmentName = attachmentService.saveAttachment(
														oneDevProject.getId(), issue.getUUID(), attachmentName, is);
												String oneDevAttachmentUrl = oneDevProject.getAttachmentUrlPath(issue.getUUID(), oneDevAttachmentName);
												attachments.add("[" + oneDevAttachmentName + "](" + oneDevAttachmentUrl + ")");
											} catch (IOException e) {
												throw new RuntimeException(e);
											} 
										}
									}
								}
							}
							
							if (!attachments.isEmpty()) {

View on GitHub (pinned to d44925c47c)

Solutions

  1. Verify the Jira API credentials used by the import have permission to download attachments from the listed content URLs.
  2. Check the reported URL manually (curl) to see the actual status — if 404, the attachment is gone and must be excluded or restored in Jira.
  3. Ensure the Jira base URL is reachable from the OneDev server (no proxy/firewall stripping auth or blocking media CDN hosts).
  4. Retry the import for transient gateway/CDN errors; if a specific attachment is permanently broken, remove it in Jira and re-run.

Example fix

// before: plain unauthenticated GET to content URL fails with 403
WebTarget target = client.target(endpoint);
// after: ensure client is built with the auth filter used by newClient()
Client client = newClient(); // registers bearer token for media downloads
WebTarget target = client.target(endpoint);
Defensive patterns

Strategy: retry

Validate before calling

try (Response r = client.target(attachmentUrl).request().get()) {
    if (r.getStatusInfo().getFamily() != Response.Status.Family.SUCCESSFUL)
        throw new IllegalStateException("Attachment not downloadable: " + r.getStatus());
}

Try / catch

try { importServer.doImport(...) } catch (ExplicitException e) { if (e.getMessage().startsWith("Error downloading attachment")) { /* check URL/status in message, verify credentials, retry */ } }

Prevention

When it happens

Trigger: consume() -> for each attachment, GET attachmentNode.get("content") returns a non-2xx response (401/403/404, expired URL, redirect to an HTML login page), so JerseyUtils.checkStatus yields a non-null errorMessage and the ExplicitException is thrown at line 807.

Common situations: Jira attachment URLs require an authenticated session/token that the plain GET does not carry; attachment deleted in Jira after issue listing (404); Jira restricts anonymous downloads; large attachments hitting a proxy/gateway error; network/CDN issues with Atlassian media URLs.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


AI-assisted analysis of theonedev/onedev@d44925c47c (2026-09-06). Data as JSON: /api/errors/1a21fee643b4427e. Report an issue: GitHub.