apolloconfig/apollo · error · ServiceException

Write app error. {}

Error message

Write app error. {}

What it means

Thrown by ConfigsExportService when serializing an App entity to JSON and writing it as a ZipEntry into the export ZipOutputStream fails with an IOException. The error wraps the underlying I/O failure (closed stream, disk full, broken pipe) and aborts the entire export operation. The '{}' placeholder is meant to receive the cause but is actually passed the exception object, not the app, due to argument ordering.

Source

Thrown at apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/ConfigsExportService.java:203

    // app admin permission filter
    return apps.stream().filter(isAppAdmin).collect(Collectors.toList());
  }

  private void writeAppInfoToZip(List<App> apps, ZipOutputStream zipOutputStream) {
    logger.info("to import app size = {}", apps.size());

    final Consumer<App> appConsumer = app -> {
      try {
        synchronized (zipOutputStream) {
          String fileName = ConfigFileUtils.genAppInfoPath(app);
          String content = gson.toJson(app);

          writeToZip(fileName, content, zipOutputStream);
        }
      } catch (IOException e) {
        logger.error("Write error. {}", app);
        throw new ServiceException("Write app error. {}", e);
      }
    };

    apps.forEach(appConsumer);
  }

  private void exportAppNamespaces(ZipOutputStream zipOutputStream) {
    List<AppNamespace> appNamespaces = appNamespaceService.findAll();

    logger.info("to import appnamespace size = {}", appNamespaces.size());

    Consumer<AppNamespace> appNamespaceConsumer = appNamespace -> {
      try {
        synchronized (zipOutputStream) {
          String fileName = ConfigFileUtils.genAppNamespaceInfoPath(appNamespace);
          String content = gson.toJson(appNamespace);

          writeToZip(fileName, content, zipOutputStream);

View on GitHub (pinned to d95fc18d11)

Solutions

  1. Check portal server disk space and ensure the temp/working directory for zip assembly has adequate free space.
  2. Verify the HTTP client is not timing out or disconnecting before the export completes; increase client read timeout for large app counts.
  3. Inspect the portal logs for the preceding 'Write error. {app}' line which logs the specific App and the underlying IOException cause.
  4. Ensure no code path calls zipOutputStream.close() or finish() before all forEach consumers complete.

Example fix

// before
} catch (IOException e) {
  logger.error("Write error. {}", app);
  throw new ServiceException("Write app error. {}", e);
}
// after - log app AND cause, pass both args
} catch (IOException e) {
  logger.error("Write error. app={}, cause={}", app, e.getMessage());
  throw new ServiceException("Write app error. app={}, cause={}", e, app, e.getMessage());
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Before export, verify the output stream is writable and disk has space
File tempDir = new File(System.getProperty("java.io.tmpdir"));
long freeSpace = tempDir.getUsableSpace();
if (freeSpace < MIN_EXPORT_DISK_BYTES) {
  throw new IllegalStateException("Insufficient disk space for export: " + freeSpace);
}

Try / catch

try {
  configsExportService.exportData(apps, envs, outputStream);
} catch (ServiceException e) {
  if (e.getMessage().contains("Write app error")) {
    log.error("Export failed during app write, likely I/O or disk issue", e);
    // notify user, retry with smaller scope
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling the config-export API endpoint that drives writeAppInfoToZip() while the ZipOutputStream is already closed, the servlet response OutputStream has been aborted by the client, or the disk/temp directory is full. The parallel forEach on apps combined with synchronized(zipOutputStream) means a single failed write propagates as ServiceException.

Common situations: Client cancels a large export download mid-stream (broken pipe); the portal runs in a container with a small /tmp or low disk quota; an older ZipOutputStream is reused after finish() was called; concurrent export requests sharing a stream.

Related errors


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