{"record":{"id":"0d2a4c6c4881ec55","repo":"apolloconfig/apollo","slug":"write-app-error","errorCode":null,"errorMessage":"Write app error. {}","messagePattern":"Write app error\\. (.+?)","errorType":"exception","errorClass":"ServiceException","httpStatus":500,"severity":"error","filePath":"apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/ConfigsExportService.java","lineNumber":203,"sourceCode":"\n    // app admin permission filter\n    return apps.stream().filter(isAppAdmin).collect(Collectors.toList());\n  }\n\n  private void writeAppInfoToZip(List<App> apps, ZipOutputStream zipOutputStream) {\n    logger.info(\"to import app size = {}\", apps.size());\n\n    final Consumer<App> appConsumer = app -> {\n      try {\n        synchronized (zipOutputStream) {\n          String fileName = ConfigFileUtils.genAppInfoPath(app);\n          String content = gson.toJson(app);\n\n          writeToZip(fileName, content, zipOutputStream);\n        }\n      } catch (IOException e) {\n        logger.error(\"Write error. {}\", app);\n        throw new ServiceException(\"Write app error. {}\", e);\n      }\n    };\n\n    apps.forEach(appConsumer);\n  }\n\n  private void exportAppNamespaces(ZipOutputStream zipOutputStream) {\n    List<AppNamespace> appNamespaces = appNamespaceService.findAll();\n\n    logger.info(\"to import appnamespace size = {}\", appNamespaces.size());\n\n    Consumer<AppNamespace> appNamespaceConsumer = appNamespace -> {\n      try {\n        synchronized (zipOutputStream) {\n          String fileName = ConfigFileUtils.genAppNamespaceInfoPath(appNamespace);\n          String content = gson.toJson(appNamespace);\n\n          writeToZip(fileName, content, zipOutputStream);","sourceCodeStart":185,"sourceCodeEnd":221,"githubUrl":"https://github.com/apolloconfig/apollo/blob/d95fc18d112589efc09ddcbe1507047584d55251/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/ConfigsExportService.java#L185-L221","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Check portal server disk space and ensure the temp/working directory for zip assembly has adequate free space.","Verify the HTTP client is not timing out or disconnecting before the export completes; increase client read timeout for large app counts.","Inspect the portal logs for the preceding 'Write error. {app}' line which logs the specific App and the underlying IOException cause.","Ensure no code path calls zipOutputStream.close() or finish() before all forEach consumers complete."],"exampleFix":"// before\n} catch (IOException e) {\n  logger.error(\"Write error. {}\", app);\n  throw new ServiceException(\"Write app error. {}\", e);\n}\n// after - log app AND cause, pass both args\n} catch (IOException e) {\n  logger.error(\"Write error. app={}, cause={}\", app, e.getMessage());\n  throw new ServiceException(\"Write app error. app={}, cause={}\", e, app, e.getMessage());\n}","handlingStrategy":"try-catch","validationCode":"// Before export, verify the output stream is writable and disk has space\nFile tempDir = new File(System.getProperty(\"java.io.tmpdir\"));\nlong freeSpace = tempDir.getUsableSpace();\nif (freeSpace < MIN_EXPORT_DISK_BYTES) {\n  throw new IllegalStateException(\"Insufficient disk space for export: \" + freeSpace);\n}","typeGuard":null,"tryCatchPattern":"try {\n  configsExportService.exportData(apps, envs, outputStream);\n} catch (ServiceException e) {\n  if (e.getMessage().contains(\"Write app error\")) {\n    log.error(\"Export failed during app write, likely I/O or disk issue\", e);\n    // notify user, retry with smaller scope\n  }\n  throw e;\n}","preventionTips":["Monitor portal disk space and set alerts for low free space.","Configure HTTP client timeouts to be longer than the worst-case export duration.","Avoid concurrent exports that share the same OutputStream or temp directory."],"tags":["apollo-portal","config-export","io","zip","service-exception"],"backgroundTag":null,"analyzedSha":"d95fc18d112589efc09ddcbe1507047584d55251","analyzedAt":"2026-08-14T04:00:05.477Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}