Azure Devops To Jira

Azure Devops To Jira

Reading time1 min
#DevOps#Migration#ProjectManagement#AzureDevOps#Jira#Workflow

Seamless Migration: Efficiently Transitioning Work Items from Azure DevOps to Jira

Vendor lock-in, license cost spikes, or simply the need for tighter integration with Atlassian tools—whatever the driver, moving from Azure DevOps to Jira is a common (and challenging) request. Rarely, though, does simple data transfer suffice. Preserving traceability, workflow fidelity, and historical context requires careful planning and more than a CSV export.

Context Loss Is the Typical Failure Mode

In practice, these issues derail most migrations:

  • Comment history fragmentation — Only top-level descriptions move, while threaded discussions get lost or misordered.
  • Workflow mismatches — Custom states and transitions in Azure DevOps (e.g., “Ready for QA”, “Blocked”) lack obvious Jira equivalents.
  • Broken relationships — Epics, parent/child hierarchies, cross-referenced dependencies often come through unlinked—especially with automated scripts.
  • Custom data inconsistency — Fields like “Effort (Fibonacci)” or “Risk Exposure” can become unreadable or mis-categorized in Jira.

Ignore these, and user adoption tanks post-migration.


1. Map Work Item Types, States, and Fields with Precision

Skip vague 1-to-1 field mappings. Start with a tabular crosswalk of every work item type and its states, especially if you rely on inherited processes/custom XML in Azure DevOps. For example:

Azure DevOps TypeAzure StateIntended Jira TypeJira StatusNotes
User StoryActiveStoryIn Progress
BugResolvedBugDone
TaskClosedSub-taskDoneNested under story
  • Review each “custom field”. Example: Azure field “Remaining Work (hours)” can be mapped to a custom Jira number field.
  • Document all transition triggers; e.g., does “Resolved” in Azure always mean “Done” in Jira, or sometimes “Awaiting QA”?

Gotcha: Watch out for multi-select field mismatches—Jira’s custom fields can differ in allowed input types.


2. Export: Retain All History, Comments, and Attachments

The built-in Azure DevOps export (e.g., Excel) typically drops history and threaded comments. Use the Azure DevOps REST API (v6.0+ recommended) to retrieve:

  • Work items, with all revisions (GET https://dev.azure.com/{org}/{proj}/_apis/wit/workItems/{ids}?expand=all)
  • Comments, with author/time
  • Attachments — stream to disk and map filenames to attachment metadata
  • Links (parent/child, related, etc.)

Sample code block for comment extraction:

url = f'https://dev.azure.com/{org}/{proj}/_apis/wit/workItems/{item_id}/comments?api-version=6.0-preview.3'
response = requests.get(url, auth=auth)
for comment in response.json()['comments']:
    print(f"{comment['revisedBy']['displayName']}: {comment['text']}")

Export errors? Expect throttling or 413 payloads on large work items—batch requests or use continuation tokens.

Known issue: Attachment fetches occasionally 403 if permissions are restricted—grant migration user “Project Administrator” role or risk silent failures.


3. Import: Maintain Structure and Chronology in Jira

Jira Cloud’s REST API v3 supports bulk issue creation (/rest/api/3/issue/bulk), but limits batch size and rate. Sequence matters: create epics first, then stories/tasks, then sub-tasks and dependencies.

  • Import comments in original sequence, using their original author and timestamp if possible (Jira API only allows this if performed by an admin user with impersonation privileges).
  • Attachments must be re-uploaded via the /attachments endpoint, ideally streaming files in original format.
  • Use a custom field (e.g., Original Azure DevOps ID) to store legacy references for troubleshooting.

Partial import example:

jira_data = {
  "fields": {
    "project": {"key": "PROJ"},
    "summary": work_item["title"],
    "description": work_item["description"],
    "issuetype": {"name": type_map[work_item["type"]]},
    # Additional mappings...
    "customfield_10042": work_item["azure_id"]
  }
}
jira_issue = jira_api.create_issue(jira_data)
# Then push comments, attachments, links

Note: Jira attachment upload cannot de-duplicate.


4. Restore Links and Hierarchies Post-Import

Parent-child connections and other references (blocks, duplicates, relates to) cannot be re-established until Jira IDs are finalized. Post-processing is required:

  • Build a reverse-mapping table: azure_id -> jira_id
  • For each migrated issue, PATCH the appropriate link type via /issueLink
  • Epics and Stories: Use “Epic Link” and “Parent” fields for hierarchy restoration
[Azure Epic 23142]
   └─ [Story 22566]  ──> Jira Epic PROJ-100  ──> PROJ-101
        └─ [Task 22600]        (Epic Link)         (Sub-task)

Trade-off: Post-processing increases total run time, but reduces link errors. For very large migrations, chunk in 500–1000 issues max per batch.


5. Validation: Parallel Sprinting and Live Feedback

Before the cutover:

  • Run both Azure DevOps and Jira in parallel for at least one sprint.
  • Solicit direct feedback with a survey: did users lose comments, context, or see odd field mappings?
  • Raise test bugs. Look for: missing attachments, incorrect state transitions, broken references.
  • Adjust field schemes and automations based on collected issues.

Migration Pragmatics & Tips

  • Automate everything — Any manual step, even for “just a few” issues, leads to missed context.
  • Surface original Azure IDs — Users will want to trace historical records.
  • Maintain a detailed error log — The APIs will quietly skip problematic records if you ignore HTTP 4xx/5xx status in your tooling.
  • Full backup — Export raw Azure DevOps work items (JSON), attachments, and a list of process customizations before migration.
  • Schema versioning — Jira custom fields get out of sync; document your schema (e.g., with customfield_XXXXX mappings).

Final Perspective

No migration is perfect. Expect some degree of data loss—usually edge-case comments, obscure field types, or broken links from legacy processes. Still, with disciplined mapping, history retention, and iterative validation, you’ll preserve continuity and minimize disruption. Sometimes, a subset of “unmigratable” items (e.g., custom dashboards, complex queries) is left archived in PDF or added as attachments in Jira for reference.

Alternative: For highly regulated environments, consider third-party tools (e.g., Exalate, OpsHub). Experience suggests that building your own minimal tooling offers greater control, but more up-front effort.

Not perfect—good enough to run production sprints. That’s the engineering benchmark.


Side note: If you’re scripting this migration, test on a non-production Jira project first to avoid flooding prod with test artifacts—Jira Cloud Free tier works, but throttles above 500 calls/hour and limits automation.


For reference, here’s a minimal, commented Python workflow script:

# Authenticate with both Azure DevOps and Jira APIs
# 1. Export Azure DevOps items
# 2. Map and transform items and fields as per migration spec
# 3. Bulk-create Jira issues
# 4. Re-establish all dependencies/links
# 5. Attach comments and files

# Run in dry-run mode first; diff outputs against original system

Questions about automating specific field mappings or managing permissions? Drop a comment. Real-world examples available on request.