title: Best practices to upload large files by chunks in Spring boot
body: <p>I have A big file and i want to upload that in Server side. it's very important when occured any problem (like interrupting the internet or power cut ...) if i retry to upload, file uploaded from <strong>resume</strong> and doesn't need to send file from beginning.</p>
  
  <p>I try this approach with sending file <strong>chunks</strong> but it seems that's not a good way, because a send chunks(byte arrays) directly in response Entity and this isn't good idea.</p>
  
  <p>whatever if anybody can develop this approach and make this code a better code with better performance i appreciate that. does anybody known <strong>Best practice way</strong> to doing that??  </p>
  
  <p>and if u like my code, vote me
  thanks :)</p>
  
  <p><strong><em>RestController</em></strong></p>
  
  <pre class="lang-java prettyprint-override"><code>@RestController
  @RequestMapping("/files")
  public class Controller {
  
  @Autowired
  private MyService service;
  
  @PutMapping("/upload/resume")
  public Mono&lt;ResponseEntity&gt; uploadWithResume(@RequestPart("chunk")byte[] chunk,
                                               @RequestPart("fileName")String fileName,
                                               @RequestParam("length")Long length
  ) throws ParseException {
      try {
          return service.fileResumeUpload(chunk, fileName, length);
      } catch (IOException e) {
          e.printStackTrace();
          return Mono.just(ResponseEntity.status(HttpStatus.PERMANENT_REDIRECT).build());
      }
  }
  
  @RequestMapping(value = "/get/uploaded/size", method = RequestMethod.HEAD)
  public Mono&lt;ResponseEntity&gt; getUploadedSize(@RequestParam("fileName") String fileName) throws IOException {
      if (Files.exists(Paths.get("src/main/resources/" + fileName))) {
  
          String size = String.valueOf(Files.size(Paths.get("src/main/resources/" + fileName)));
  
          return Mono.just(ResponseEntity.ok()
                  .header("upload-offset", size)
                  .build());
      } else{
          return Mono.just(ResponseEntity.notFound()
                  .header("upload-offset" , "0").build());
      }
  }
  }
  </code></pre>
  
  <p><strong><em>Service</em></strong></p>
  
  <pre class="lang-java prettyprint-override"><code>public Mono&lt;ResponseEntity&gt; fileResumeUpload(byte[] chunk , String fileName,long length) throws IOException, ParseException {
      BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream("src/main/resources/" + fileName, true));
  
      boolean uploaded = true;
  
      try {
          out.write(chunk);
      } catch (IOException e) {
          uploaded = false;
          System.err.println("io exception");
      } finally {
          if (uploaded) {
              out.close();
                  return Mono.just(ResponseEntity.ok()
                          .header("expiration-date", getExpirationDate())
                          .build());
          } else {
              out.close();
              return Mono.just(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build());
          }
      }
  }
  </code></pre>
  
  <p><strong><em>Sending chunks with webTestClient</em></strong></p>
  
  <pre class="lang-java prettyprint-override"><code>  @Test
  public void test1_upload_Expected_200StatusCode(){
      try {
  
          String fileName = "film.mkv";
  
          RandomAccessFile raf = new RandomAccessFile(new File("src/test/resources/" + fileName), "rw");
          long realSize = raf.length();
          List&lt;String&gt; strings = webTestClient.head().uri("/files/get/uploaded/size?fileName=" + fileName)
                  .exchange().expectBody().returnResult().getResponseHeaders().get("upload-offset");
  
          long uploadedSize = Long.valueOf(strings.get(0));
  
          boolean f = false;
  
          int sizeBuffer = 256 * 1024;
          byte[] buffer = new byte[sizeBuffer];
  
          MultiValueMap&lt;String, Object&gt; formData;
  
          WebTestClient.ResponseSpec exchange = null;
  
          System.out.println("first uploaded Size ; " + uploadedSize);
  
          raf.seek(uploadedSize);
          while (raf.read(buffer) != -1) {
              formData = new LinkedMultiValueMap&lt;&gt;();
              formData.add("fileName", fileName);
              formData.add("chunk", buffer);
              formData.add("length", realSize);
  
              exchange = webTestClient.put().uri("/files/upload/resume")
                      .contentType(MediaType.MULTIPART_FORM_DATA)
                      .body(BodyInserters.fromMultipartData(formData))
                      .exchange();
              exchange.expectStatus().isOk();
              if (exchange.expectBody().returnResult().getStatus().is5xxServerError()) {
                  return;
              }
  
  
              if (uploadedSize + 256 * 1024 &gt; realSize) {
                  sizeBuffer = ((int) (realSize - uploadedSize));
                  System.out.println(sizeBuffer);
                  uploadedSize = uploadedSize + sizeBuffer;
                  System.out.println(uploadedSize);
                  buffer = new byte[sizeBuffer];
                  f=true;
              } else uploadedSize = uploadedSize + sizeBuffer;
  
              if (f) System.out.println(uploadedSize);
              //System.out.println(uploadedSize);
  
              float percent = ((float) uploadedSize / realSize * 100);
              System.out.format("%.2f\n", percent);
          }
          if (exchange!=null)
              exchange.expectStatus().isOk();
      }
      catch (Exception e){
          e.printStackTrace();
          System.err.println("channel closed!!!");
      }
  }
  </code></pre>