{"record":{"id":"94574c5ca95c131f","repo":"apache/druid","slug":"s3storageconfig-cannot-be-null","errorCode":null,"errorMessage":"S3StorageConfig cannot be null!","messagePattern":"S3StorageConfig cannot be null!","errorType":"validation","errorClass":"IllegalStateException (ISE)","httpStatus":null,"severity":"error","filePath":"extensions-core/s3-extensions/src/main/java/org/apache/druid/storage/s3/ServerSideEncryptingAmazonS3.java","lineNumber":546,"sourceCode":"     * Builds a new {@link ServerSideEncryptingAmazonS3} instance.\n     *\n     * <p><b>Resource leak warning:</b> Each instance created by this method holds internal resources such as thread\n     * pools and connection pools. {@link ServerSideEncryptingAmazonS3} is not {@link java.io.Closeable}, so there is\n     * currently no way for callers to release these resources when the instance is no longer needed. Avoid calling\n     * this method repeatedly (e.g., once per file or per task) when a single shared instance would suffice. Consider\n     * memoizing the result, as {@link org.apache.druid.data.input.s3.S3InputSource} does, to ensure the client is\n     * created at most once per configuration.\n     *\n     * <p>The long-term fix is to make {@link ServerSideEncryptingAmazonS3} implement {@link java.io.Closeable} and\n     * arrange for {@code close()} to be called appropriately, but that is a larger change deferred for the future.\n     */\n    public ServerSideEncryptingAmazonS3 build()\n    {\n      if (s3ClientSupplier == null) {\n        throw new ISE(\"S3Client supplier cannot be null!\");\n      }\n      if (s3StorageConfig == null) {\n        throw new ISE(\"S3StorageConfig cannot be null!\");\n      }\n\n      S3Client s3Client;\n      try {\n        s3Client = S3Utils.retryS3Operation(s3ClientSupplier::get);\n      }\n      catch (Exception e) {\n        throw new RuntimeException(e);\n      }\n\n      S3AsyncClient s3AsyncClient = null;\n      if (s3AsyncClientSupplier != null) {\n        try {\n          s3AsyncClient = S3Utils.retryS3Operation(s3AsyncClientSupplier::get);\n        }\n        catch (Exception e) {\n          log.warn(e, \"Failed to create S3AsyncClient, falling back to sync uploads\");\n        }","sourceCodeStart":528,"sourceCodeEnd":564,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/extensions-core/s3-extensions/src/main/java/org/apache/druid/storage/s3/ServerSideEncryptingAmazonS3.java#L528-L564","documentation":"ServerSideEncryptingAmazonS3.Builder.build() validates that both an S3 client supplier and an S3StorageConfig (encryption config) were provided before constructing the client. The S3StorageConfig carries server-side encryption settings (SSE-C keys, KMS/AES256 config) needed to wrap every S3 request. A null config means the builder was never given the encryption configuration, so requests would be sent without the required encryption material.","triggerScenarios":"Calling ServerSideEncryptingAmazonS3.builder().build() without invoking .withS3StorageConfig(...), or programmatically constructing the S3 client in an extension/task where the druid.storage.s3 serverSideEncryption config properties were absent so the config bean was never populated.","commonSituations":"Custom ingestion tasks or test harnesses building the S3 client manually; Druid configs missing serverSideEncryption.* properties in runtime.properties; upgrades where a module that previously injected a default (NoopServerSideEncryptingAmazonS3) config no longer does.","solutions":["Set the server-side encryption properties (e.g. druid.storage.s3.serverSideEncryptionType and related key properties) so the config is populated.","When building programmatically, call builder().withS3StorageConfig(new ServerSideEncryptingAmazonS3.NoopServerSideEncrypting()) if no encryption is desired.","Verify the extension wiring/config bean for S3StorageConfig is injected into the code path constructing the builder.","Add an early validation in your module setup that fails with a clearer message if encryption config is absent."],"exampleFix":"// before\nServerSideEncryptingAmazonS3 s3 = ServerSideEncryptingAmazonS3.builder()\n    .setS3ClientSupplier(supplier)\n    .build(); // throws: S3StorageConfig cannot be null!\n// after\nServerSideEncryptingAmazonS3 s3 = ServerSideEncryptingAmazonS3.builder()\n    .setS3ClientSupplier(supplier)\n    .withS3StorageConfig(new ServerSideEncryptingAmazonS3.NoopServerSideEncrypting())\n    .build();","handlingStrategy":"validation","validationCode":"if (config == null) {\n  throw new IllegalArgumentException(\"s3StorageConfig must be set before build(); check serverSideEncryption config properties\");\n}\nServerSideEncryptingAmazonS3 s3 = ServerSideEncryptingAmazonS3.builder()\n    .setS3ClientSupplier(supplier)\n    .withS3StorageConfig(config)\n    .build();","typeGuard":"boolean hasStorageConfig(ServerSideEncryptingAmazonS3.Builder b) { return b != null; } // builder API: ensure withS3StorageConfig called; verify via config bean != null","tryCatchPattern":"try { s3 = builder.build(); } catch (IllegalStateException e) { if (e.getMessage().contains(\"S3StorageConfig\")) { /* supply config and retry */ } else throw e; }","preventionTips":["Always set serverSideEncryption.* properties when S3 deep storage is used.","Use Druid's injected S3StorageConfig bean rather than constructing the builder by hand.","Add a unit test asserting client construction with your module's config.","Default to NoopServerSideEncryptingAmazonS3 explicitly when encryption is not required."],"tags":["aws-s3","configuration","encryption","druid"],"backgroundTag":"missing-required-config-field","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}