Audience — App developers and Marketplace vendors. This guide explains how to detect Jira Data Center read-only mode and make your app respect it.
When a Jira Data Center administrator enables read-only mode, Jira blocks write operations that flow through its project-permission model. Apps are not automatically compatible: if your app writes data through its own code paths — managers, direct SQL, background jobs, event listeners, webhooks, or user impersonation — those writes will continue unless you guard them yourself.
This guide shows you how to detect read-only mode and where to add guards so your app behaves correctly during a customer's maintenance or migration window.
Read-only mode is enforced in one place: the project-permission check in DefaultPermissionManager. The rule is simple — for a non-system-administrator, a project permission is granted only if it is marked as available in read-only mode (reads are; writes aren't).
System administrators are exempt from read-only mode. Keep that in mind: the bypass is meant for a human admin deliberately performing maintenance, not for automated code that happens to run as an admin actor.
That means:
IssueService/PermissionManager and honours EDIT_ISSUES before writing), it is already blocked for non-admins when read-only mode is on. You get compatibility for free.There are three supported ways to check the current state. Use whichever fits your extension point.
1 2GET /rest/api/2/readonly-mode
1 2{ "enabled": true, "message": "Jira is currently in read-only mode for maintenance.", "endTime": "2026-06-30T18:00", "timeZone": "Asia/Beirut" }
This endpoint is available to any licensed user, so front-end code and external integrations can call it to adapt their behaviour.
1 2applicationProperties.getOption(APKeys.JIRA_OPTION_READ_ONLY_MODE); // true when read-only mode is on
The property key is jira.option.readonly.mode. This is the cheapest check for back-end code.
Jira publishes com.atlassian.jira.event.ReadOnlyModeChangedEvent whenever an admin turns read-only mode on or off. Subscribe if your app needs to react — for example, to pause a scheduler, flush a cache, or update a status indicator.
1 2@EventListener public void onReadOnlyModeChanged(ReadOnlyModeChangedEvent event) { if (event.isEnabled()) { // read-only mode was turned ON — pause writes } else { // read-only mode was turned OFF — resume normal operation } }
The event is @ExperimentalApi. Guard against API changes and don't rely on ordering relative to other listeners.
Audit every place your app can write. Typical surfaces that bypass the read-only gate and need an explicit check:
PermissionManager.There are exactly two patterns. Never confuse them.
| Pattern 1 — Interactive (with bypass) | Pattern 2 — Automated (unconditional) | |
|---|---|---|
| Use for | REST write endpoints and admin UI actions triggered by a logged-in human | Execution pipelines, scheduled jobs, incoming webhooks, event listeners |
| Sysadmin bypass? | Yes — a system administrator may proceed | No — block regardless of actor |
| When to check | Before any data modification, while the thread user is the logged-in human | Before any impersonation happens, at the entry point |
1 2boolean isReadOnlyModeActiveForCurrentUser() { if (!applicationProperties.getOption(APKeys.JIRA_OPTION_READ_ONLY_MODE)) { return false; } // Check SYSTEM_ADMIN, not ADMINISTER — plain Jira admins must still be blocked. return !globalPermissionManager.hasPermission( GlobalPermissionKey.SYSTEM_ADMIN, authenticationContext.getLoggedInUser()); }
Use SYSTEM_ADMIN, not ADMINISTER. A plain "Jira admin" (ADMINISTER) must be blocked in read-only mode — only system administrators are exempt. Reusing an existing isAdmin()/isJiraAdmin() helper is a common mistake.
Call this at the top of each mutating REST method and return 503 Service Unavailable (or your API's equivalent) when it returns true.
1 2if (applicationProperties.getOption(APKeys.JIRA_OPTION_READ_ONLY_MODE)) { // No actor check — automated writes are blocked regardless of who configured them. return; // or reject the request (e.g. 503) for an inbound webhook }
Use this for anything that runs without a deliberate human action: scheduled jobs, event-driven execution, and unauthenticated webhooks.
If your app runs rules or actions as a configured actor (impersonation), the guard placement matters. Consider a pipeline that does:
1 2event → executeAs(actorAccountId, runnable) → runRule → write to Jira
After executeAs(), the thread's user is the rule's actor — not the human who triggered it. If you check read-only mode inside runRule() and the actor happens to be a sysadmin, the sysadmin bypass would incorrectly let the write through.
Correct approach: apply the unconditional guard (Pattern 2) before impersonation starts — at the top of the execution entry point, before executeAs().
Design principle: the sysadmin bypass is for deliberate, interactive actions. A rule's actor being a sysadmin is configuration set days ago — it does not represent an admin consciously choosing to write during a read-only window. Automated execution must be blocked unconditionally, regardless of the actor's identity.
The same reasoning applies to scheduled jobs (which run with a null user) and to unauthenticated inbound webhooks (which have no user at all): there's no one to "bypass", so the block must be unconditional.
A back-end guard that silently returns 503 is confusing UX. If your app has UI, also make it read-only-aware so users understand why an action failed:
GET /rest/api/2/readonly-mode (or read a context flag you inject) and, when enabled is true:
message and endTime.isReadOnly flag in your context payload so the front end can render correctly instead of letting a save silently fail at the REST layer.The REST guards are your actual enforcement; the front-end changes just prevent confusing silent failures.
Cover these scenarios with read-only mode toggled on and off. Impersonation makes the actor identity the tricky part:
SYSTEM_ADMIN not ADMINISTER).ReadOnlyModeChangedEvent to react to state changes.Rate this page: