<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Vrinda Maru Kansal</title>
    <description>The latest articles on DEV Community by Vrinda Maru Kansal (@vrindamarukansal).</description>
    <link>https://dev.to/vrindamarukansal</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3865470%2F25e3d9c5-d478-47df-8fdb-b500f3719c05.jpeg</url>
      <title>DEV Community: Vrinda Maru Kansal</title>
      <link>https://dev.to/vrindamarukansal</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vrindamarukansal"/>
    <language>en</language>
    <item>
      <title>How We Upload Multi-GB Files via REST (Without Blowing Up Memory)</title>
      <dc:creator>Vrinda Maru Kansal</dc:creator>
      <pubDate>Tue, 07 Apr 2026 12:10:34 +0000</pubDate>
      <link>https://dev.to/vrindamarukansal/how-we-upload-multi-gb-files-via-rest-without-blowing-up-memory-33cg</link>
      <guid>https://dev.to/vrindamarukansal/how-we-upload-multi-gb-files-via-rest-without-blowing-up-memory-33cg</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Originally published on &lt;a href="https://vrindakansal.hashnode.dev/handling-multi-gigabyte-files-via-rest-api-a-stream-oriented-architecture" rel="noopener noreferrer"&gt;Hashnode&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The Challenge&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Processing large payloads through REST APIs presents a fundamental challenge: how do you accept, validate, encrypt, and store multi-gigabyte data streams without exhausting server memory or degrading response times? Traditional approaches that buffer entire payloads into memory fail spectacularly when data sizes exceed available heap space. The naive solution of simply increasing memory creates a cascading problem—fewer concurrent requests, higher infrastructure costs, and unpredictable OutOfMemoryError failures.&lt;/p&gt;

&lt;p&gt;On the other hand, the typical “upload first, validate later” pattern used by many large systems fails to provide client feedback within the same request.&lt;/p&gt;

&lt;h2&gt;
  
  
  An Elegant Solution: The Streaming Pipeline
&lt;/h2&gt;

&lt;p&gt;Stream-oriented architecture can process arbitrarily large payloads with constant, minimal memory overhead. The secret lies in treating the data as a continuous flow of bytes rather than a discrete object to be loaded into memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture Overview
&lt;/h2&gt;

&lt;p&gt;When a client initiates a request to upload data, the service orchestrates a sophisticated pipeline of composed stream decorators. Each decorator in the chain performs a specific responsibility while passing bytes through to the next stage—all without buffering the entire payload.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv2w58dtj4gxg7sv4tsws.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv2w58dtj4gxg7sv4tsws.png" alt="Uploading large files via Rest API" width="800" height="1200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For simplicity, this article use CSV files as example data format. The architecture applies to any structured or semi-structured format&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  The Pipeline Stages
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP Request Body (InputStream)
    ↓
ValidationTeeStream            ← Splits to background validator (validates &amp;amp; counts records)
    ↓
EncryptionStream               ← Encryption (e.g., AES-GCM)
    ↓
Cloud Storage Upload           ← Parallel multipart upload
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each byte flows through every stage exactly once. No rewinding, no temporary files, no memory accumulation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deep Dive: Component Responsibilities
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. &lt;strong&gt;Validation Tee Stream&lt;/strong&gt;: Background Validation and Record Counting
&lt;/h3&gt;

&lt;p&gt;Perhaps the most innovative component is the validation tee. The service must validate CSV structure (correct column count, parseable formats) and count records before committing the upload, but doing so in-line would require two passes through the data stream.&lt;/p&gt;

&lt;p&gt;The solution? Duplicate the byte stream using a pipe, sending a copy to a background thread that both parses and counts records asynchronously:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Pseudocode: Stream decorator that tees data to validation pipeline&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ValidationTeeStream&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;FilterInputStream&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;ValidationPipeline&lt;/span&gt; &lt;span class="n"&gt;validationPipeline&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;bytesRead&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bytesRead&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Send copy to background validator&lt;/span&gt;
            &lt;span class="n"&gt;validationPipeline&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;validationSink&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;write&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bytesRead&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;bytesRead&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a "T" junction in the stream, splitting the data flow into two paths:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Main stream path&lt;/strong&gt; (continues in main thread): Proceeds to encryption and cloud storage upload&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Validation stream path&lt;/strong&gt; (background thread): Feeds a parser that validates data structure and perform other operations like counting records&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The validation runs in parallel with the upload. The background thread performs both validation and record counting as it parses the data. If errors are detected, the validation pipeline captures them and raises an exception when the stream closes—aborting the multipart upload before it completes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Pseudocode: Validation pipeline waits for background thread and checks results&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;throwIfFailed&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ValidationError&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;backgroundValidationTask&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;await&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isEmpty&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;ValidationError&lt;/span&gt; &lt;span class="n"&gt;firstError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ValidationException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"Data validation failed: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;firstError&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getMessage&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This clever design enables &lt;strong&gt;fail-fast validation with integrated record counting&lt;/strong&gt; without sacrificing the single-pass streaming architecture. The background thread naturally counts records as it parses them, eliminating the need for a separate counting mechanism.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;strong&gt;Encryption Stream&lt;/strong&gt;: Transparent Encryption
&lt;/h3&gt;

&lt;p&gt;Data is encrypted at rest using industry-standard encryption algorithms like AES-GCM. Instead of encrypting the entire plaintext in memory, the stream is wrapped with an encryption decorator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Pseudocode: Encryption stream wrapper&lt;/span&gt;
&lt;span class="nc"&gt;Cipher&lt;/span&gt; &lt;span class="n"&gt;cipher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;initializeCipher&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encryptionKey&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;initializationVector&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;EncryptedInputStream&lt;/span&gt; &lt;span class="n"&gt;encryptedStream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CipherInputStream&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plaintextStream&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cipher&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cipher processes data in blocks as it's read. Memory usage remains bounded to the cipher's internal buffer size (typically 16KB), regardless of payload size.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;strong&gt;Cloud Storage Multipart Upload&lt;/strong&gt;: Parallel Network Transfer
&lt;/h3&gt;

&lt;p&gt;The final stage uploads encrypted data to cloud storage (e.g., AWS S3, Azure Blob, Google Cloud Storage) using multipart upload with &lt;strong&gt;parallel part uploads and backpressure&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Pseudocode: Parallel multipart upload with backpressure&lt;/span&gt;
&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;partSize&lt;/span&gt;&lt;span class="o"&gt;];&lt;/span&gt;  &lt;span class="c1"&gt;// e.g., 16MB&lt;/span&gt;
&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Future&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;UploadedPart&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;uploadFutures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ArrayList&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="n"&gt;bytesRead&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;readFully&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inputStream&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;partNumber&lt;/span&gt;&lt;span class="o"&gt;++;&lt;/span&gt;
    &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;partDataCopy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Arrays&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;copyOf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bytesRead&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Upload part asynchronously&lt;/span&gt;
    &lt;span class="nc"&gt;Future&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;UploadedPart&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;future&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;submit&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cloudStorageClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uploadPart&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uploadId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;partNumber&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;partDataCopy&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;});&lt;/span&gt;

    &lt;span class="n"&gt;uploadFutures&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;future&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Backpressure: wait for oldest upload if queue is full&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uploadFutures&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;maxConcurrentUploads&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;uploadFutures&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uploadFutures&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;maxConcurrentUploads&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Wait for all uploads to complete&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Future&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;UploadedPart&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;future&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;uploadFutures&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;future&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Finalize the multipart upload&lt;/span&gt;
&lt;span class="n"&gt;cloudStorageClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;completeMultipartUpload&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uploadId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uploadedParts&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This implementation achieves optimal throughput by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Sequential reads&lt;/strong&gt;: The input stream is read serially to avoid concurrency issues&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Parallel uploads&lt;/strong&gt;: Parts are uploaded concurrently using a thread pool&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Bounded memory&lt;/strong&gt;: Backpressure ensures at most &lt;code&gt;maxConcurrentUploads&lt;/code&gt; parts (e.g., 4 × 16MB = 64MB) exist in memory simultaneously&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The parallelism overlaps network I/O, dramatically reducing upload times for large payloads over high-latency connections.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Backpressure implementation will be covered in detail in a follow-up article&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Memory Efficiency Analysis
&lt;/h2&gt;

&lt;p&gt;Let's calculate the maximum memory footprint for a 5GB payload upload:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Memory Usage&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Validation pipe buffer&lt;/td&gt;
&lt;td&gt;1 MB (configurable)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Encryption cipher buffer&lt;/td&gt;
&lt;td&gt;16 KB (typical)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Upload parts (4 parallel × 16MB)&lt;/td&gt;
&lt;td&gt;64 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~65 MB&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Compare this to a naive approach that loads the entire payload: &lt;strong&gt;5,000 MB&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The streaming architecture achieves a &lt;strong&gt;75× reduction in memory consumption&lt;/strong&gt;—transforming an intractable problem into a trivially scalable one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Request Flow: A 1GB Upload Example
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Client&lt;/strong&gt; initiates data upload via HTTP PUT/POST request&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Controller&lt;/strong&gt; extracts request body input stream and delegates to service layer&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Service orchestrates pipeline&lt;/strong&gt;:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*   Starts background validator

*   Wraps stream in tee to feed validator

*   Obtains encryption key from key management service

*   Wraps stream in encryption decorator

*   Wraps in validation-failure-on-close wrapper
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Cloud storage upload begins&lt;/strong&gt;:&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*   Reads chunks (e.g., 16MB) from encrypted stream

*   Uploads multiple parts in parallel

*   Applies backpressure to throttle producer to match consumer's pace

*   Continues until stream exhausted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Validation completes&lt;/strong&gt;:&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*   Background thread finishes parsing and counting records

*   Any errors captured in validation pipeline
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Stream closes&lt;/strong&gt;:&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*   Triggers validation check (throws if invalid)

*   Retrieves record count from validator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Metadata persisted&lt;/strong&gt;:&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*   Database stores: storage key, record count, blob storage path
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Response returned&lt;/strong&gt;: Success message with upload ID and statistics&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Total time: ~30 seconds for 1GB payload over typical connection. Memory used: &amp;lt;100MB regardless of payload size.&lt;/p&gt;

&lt;h2&gt;
  
  
  Error Handling and Atomicity
&lt;/h2&gt;

&lt;p&gt;The pipeline guarantees atomic failure modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Validation failure&lt;/strong&gt;: Validation pipeline throws on stream close → multipart upload is &lt;strong&gt;aborted&lt;/strong&gt; → no orphaned data&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Encryption error&lt;/strong&gt;: Exception propagates immediately → multipart upload aborted&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Upload failure&lt;/strong&gt;: Any part failure triggers abort operation → no partial data&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Database transaction rollback&lt;/strong&gt;: If metadata persistence fails, cleanup service removes uploaded data&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The stream composition pattern naturally enforces cleanup—each wrapper's &lt;code&gt;close()&lt;/code&gt; method can trigger rollback operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Characteristics
&lt;/h2&gt;

&lt;p&gt;Typical performance on a modern cloud instance (2 vCPU, 8GB RAM):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Payload Size&lt;/th&gt;
&lt;th&gt;Upload Time&lt;/th&gt;
&lt;th&gt;Peak Memory&lt;/th&gt;
&lt;th&gt;Throughput&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;100 MB&lt;/td&gt;
&lt;td&gt;3.2s&lt;/td&gt;
&lt;td&gt;68 MB&lt;/td&gt;
&lt;td&gt;31 MB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;500 MB&lt;/td&gt;
&lt;td&gt;14.8s&lt;/td&gt;
&lt;td&gt;71 MB&lt;/td&gt;
&lt;td&gt;34 MB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1 GB&lt;/td&gt;
&lt;td&gt;28.5s&lt;/td&gt;
&lt;td&gt;73 MB&lt;/td&gt;
&lt;td&gt;36 MB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5 GB&lt;/td&gt;
&lt;td&gt;141.2s&lt;/td&gt;
&lt;td&gt;76 MB&lt;/td&gt;
&lt;td&gt;36 MB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10 GB&lt;/td&gt;
&lt;td&gt;279.4s&lt;/td&gt;
&lt;td&gt;78 MB&lt;/td&gt;
&lt;td&gt;36 MB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Note the &lt;strong&gt;constant memory usage&lt;/strong&gt; regardless of payload size—the hallmark of a well-designed streaming architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Architectural Principles
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. &lt;strong&gt;Composition over Inheritance&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Each &lt;code&gt;FilterInputStream&lt;/code&gt; subclass does one thing. Complex behavior emerges from composing simple decorators.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;strong&gt;Lazy Evaluation&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Data is processed only when pulled by the consumer (cloud storage upload). No eager buffering.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;strong&gt;Backpressure&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Cloud storage upload limits in-flight parts, preventing unbounded memory growth from fast producers.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. &lt;strong&gt;Single Responsibility&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;ValidationTeeStream&lt;/code&gt;: Validation and record counting&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;EncryptionStream&lt;/code&gt;: Encryption&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;ValidationPipeline&lt;/code&gt;: Background parsing coordinator&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;StorageService&lt;/code&gt;: Multipart upload orchestration&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each component is independently testable and replaceable.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. &lt;strong&gt;Fail-Fast Validation&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Background validation runs concurrently with upload but aborts before completion if errors detected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons for API Developers
&lt;/h2&gt;

&lt;p&gt;This architecture demonstrates several transferable patterns:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Never buffer large payloads&lt;/strong&gt;: Use the request body input stream directly without buffering&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Compose functional pipelines&lt;/strong&gt;: Chain stream decorators for orthogonal concerns&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Parallelize I/O, not computation&lt;/strong&gt;: Upload parts concurrently while reading serially&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Validate asynchronously&lt;/strong&gt;: Tee streams to background validators without blocking main path&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Enforce atomicity at stream boundaries&lt;/strong&gt;: Use &lt;code&gt;close()&lt;/code&gt; hooks for cleanup and validation checks&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This architecture demonstrates that RESTful services can handle arbitrarily large data uploads without sacrificing memory efficiency, validation rigor, or security. By embracing stream-oriented programming and carefully composing stream decorators, the solution transforms a challenging scalability problem into an elegant, maintainable architecture.&lt;/p&gt;

&lt;p&gt;The core insight: &lt;strong&gt;data doesn't need to be in memory to be processed&lt;/strong&gt;. It just needs to flow through the right sequence of transformations.&lt;/p&gt;

&lt;p&gt;This architecture scales horizontally (each instance handles large payloads independently) and vertically (memory consumption is constant, not proportional to payload size). As data sizes grow to 50GB, 100GB, or beyond, the system handles them with the same minimal memory footprint.&lt;/p&gt;

&lt;p&gt;Stream on.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Example Technologies&lt;/strong&gt;: Java Streams API, Spring Boot, Cloud Storage SDKs (AWS S3, Azure Blob, GCS), Industry-standard Encryption Libraries&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Core Components&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Request handler (controller layer)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Service orchestration layer&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Stream validation decorators&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Encryption stream wrappers&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cloud storage multipart upload clients&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Performance&lt;/strong&gt;: Processes 10GB payloads with &amp;lt;80MB memory footprint&lt;/p&gt;

</description>
      <category>microservices</category>
      <category>java</category>
      <category>architecture</category>
      <category>restapi</category>
    </item>
  </channel>
</rss>
