apolloconfig/apollo · warning · BadRequestException

namespace:%s is modified by %s

Error message

namespace:%s is modified by %s

What it means

A BadRequestException (HTTP 400) thrown from NamespaceAcquireLockAspect.checkLock() when a namespace lock exists but is owned by a different user. Apollo enforces single-writer-per-namespace-per-release via the NamespaceLock table (created_by = lock holder). When the current operator does not match the lock owner, the write is rejected. This is an intentional concurrency-conflict signal, not a bug.

Source

Thrown at apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/aop/NamespaceAcquireLockAspect.java:164

  }

  private void tryLock(long namespaceId, String user) {
    NamespaceLock lock = new NamespaceLock();
    lock.setNamespaceId(namespaceId);
    lock.setDataChangeCreatedBy(user);
    lock.setDataChangeLastModifiedBy(user);
    namespaceLockService.tryLock(lock);
  }

  private void checkLock(Namespace namespace, NamespaceLock namespaceLock, String currentUser) {
    if (namespaceLock == null) {
      throw new ServiceException(
          String.format("Check lock for %s failed, please retry.", namespace.getNamespaceName()));
    }

    String lockOwner = namespaceLock.getDataChangeCreatedBy();
    if (!lockOwner.equals(currentUser)) {
      throw new BadRequestException(
          "namespace:" + namespace.getNamespaceName() + " is modified by " + lockOwner);
    }
  }


}

View on GitHub (pinned to d95fc18d11)

Solutions

  1. Coordinate with the lock owner (named in the error message) to finish or abort their edit session.
  2. If the lock is stale (owner is no longer active), delete the NamespaceLock row from the database for that namespaceId to release it.
  3. Disable namespace.lock.switch in biz config if single-writer enforcement is not needed for your workflow.
  4. Serialize writes from the same integration so a single operator consistently acquires the lock.

Example fix

// before: two different operators editing same namespace
itemDTO.setDataChangeLastModifiedBy("userB");
openApi.updateItem(appId, env, cluster, namespace, itemId, itemDTO);

// after: use a consistent operator / service account for automated edits
itemDTO.setDataChangeLastModifiedBy("ci-bot");
openApi.updateItem(appId, env, cluster, namespace, itemId, itemDTO);

// to release a stale lock manually (DB-level):
// DELETE FROM NamespaceLock WHERE namespaceId = <id>;
Defensive patterns

Strategy: validation

Validate before calling

// Before writing, check who currently holds the lock
NamespaceLock currentLock = namespaceLockService.findLock(namespace.getId());
if (currentLock != null && !operator.equals(currentLock.getDataChangeCreatedBy())) {
    // warn user or queue the edit; do not proceed with the write
    throw new IllegalStateException("Namespace is locked by " + currentLock.getDataChangeCreatedBy());
}

Try / catch

try {
    openApi.updateItem(appId, env, cluster, namespace, itemId, itemDTO);
} catch (BadRequestException e) {
    if (e.getMessage().contains("is modified by")) {
        // Notify user of concurrent edit conflict; do NOT auto-retry
        String lockOwner = extractOwnerFromMessage(e.getMessage());
        notifyConflict(namespaceName, lockOwner);
    } else {
        throw e;
    }
}

Prevention

When it happens

Trigger: Any item create/update/delete or change-set operation on a namespace that another user has already locked (namespace.lock.switch enabled). The lock owner is namespaceLock.getDataChangeCreatedBy(); currentUser is derived from dto.getDataChangeLastModifiedBy() or the operator parameter.

Common situations: Two operators editing the same config namespace simultaneously in different browser tabs or via API; a previous edit session crashed without releasing the lock; automated CI/CD pipelines and manual edits colliding on the same namespace.

Related errors


AI-assisted analysis of apolloconfig/apollo@d95fc18d11 (2026-08-14). Data as JSON: /api/errors/911c3f1684248eff. Report an issue: GitHub.