class S3OutputStream extends OutputStream {

        private static final int PART_SIZE = 5 * 1024 * 1024;

        private final AmazonS3 client;
        private final InitiateMultipartUploadResult multipartUpload;
        private final Executor executor;
        private final String bucketName;
        private final String key;
        private final List<CompletableFuture<PartETag>> pendingUploads = new ArrayList<>();

        private ByteArrayOutputStream outputStream;
        long currentPartBytes = 0;
        int partNumber = 1;

        private S3OutputStream(Executor executor,
                               S3URI s3URI,
                               S3ClientHelper s3ClientHelper,
                               String protocolSetting) throws IOException {
            this.executor = executor;
            bucketName = s3URI.bucket();
            key = s3URI.key();
            outputStream = new ByteArrayOutputStream();
            client = s3ClientHelper.client(s3URI, protocolSetting);
            multipartUpload = client.initiateMultipartUpload(new InitiateMultipartUploadRequest(bucketName, key));
        }

        @Override
        public void write(byte[] b) throws IOException {
            outputStream.write(b);
            currentPartBytes += b.length;
            doUploadIfNeeded();
        }

        @Override
        public void write(byte[] b, int off, int len) throws IOException {
            outputStream.write(b, off, len);
            currentPartBytes += len;
            doUploadIfNeeded();
        }

        @Override
        public void write(int b) throws IOException {
            outputStream.write(b);
            currentPartBytes++;
            doUploadIfNeeded();
        }

        private void doUploadIfNeeded() throws IOException {<FILL_FUNCTION_BODY>}

        @Override
        public void close() throws IOException {
            UploadPartRequest uploadPartRequest = new UploadPartRequest()
                .withBucketName(bucketName)
                .withKey(key)
                .withPartNumber(partNumber)
                .withPartSize(outputStream.size())
                .withUploadId(multipartUpload.getUploadId())
                .withInputStream(new ByteArrayInputStream(outputStream.toByteArray()));
            UploadPartResult uploadPartResult = client.uploadPart(uploadPartRequest);

            List<PartETag> partETags;
            try {
                partETags = CompletableFutures.allAsList(pendingUploads).get();
            } catch (InterruptedException | ExecutionException e) {
                throw new IOException(e);
            }
            partETags.add(uploadPartResult.getPartETag());
            client.completeMultipartUpload(
                new CompleteMultipartUploadRequest(
                    bucketName,
                    key,
                    multipartUpload.getUploadId(),
                    partETags)
            );
            super.close();
        }
    }

            if (currentPartBytes >= PART_SIZE) {
                final ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
                final int currentPart = partNumber;
                final long currentPartSize = currentPartBytes;

                outputStream.close();
                outputStream = new ByteArrayOutputStream();
                partNumber++;
                pendingUploads.add(CompletableFutures.supplyAsync(() -> {
                    UploadPartRequest uploadPartRequest = new UploadPartRequest()
                        .withBucketName(bucketName)
                        .withKey(key)
                        .withPartNumber(currentPart)
                        .withPartSize(currentPartSize)
                        .withUploadId(multipartUpload.getUploadId())
                        .withInputStream(inputStream);
                    return client.uploadPart(uploadPartRequest).getPartETag();
                }, executor));
                currentPartBytes = 0;
            }
        
<no_super_class>