Eğitim Portalı/Spring Boot/Spring Boot Dosya İşleme (Upload / Download)
Spring Boot03-spring-boot/17-file-handling

Spring Boot Dosya İşleme (Upload / Download)

Birçok uygulama dosya alıp verir: profil fotoğrafı yükleme, CSV/Excel içe aktarma, PDF/rapor indirme, belge yönetimi. Spring Boot, dosya yükleme (multipart) ve indirme işlemlerini basit anotasyonlarla destekler. Bu ko…

Spring Boot Dosya İşleme (Upload / Download)

Birçok uygulama dosya alıp verir: profil fotoğrafı yükleme, CSV/Excel içe aktarma, PDF/rapor indirme, belge yönetimi. Spring Boot, dosya yükleme (multipart) ve indirme işlemlerini basit anotasyonlarla destekler. Bu konu, dosya yükleme ve indirmeyi ve üretimdeki dikkat noktalarını ele alır.

Dosya yükleme: MultipartFile

Tarayıcı/istemci, dosyayı multipart/form-data olarak gönderir; Spring bunu MultipartFile'a bağlar:

@PostMapping("/yukle")
public Map<String,Object> yukle(@RequestParam("dosya") MultipartFile dosya) {
    dosya.getOriginalFilename();   // istemcideki ad
    dosya.getSize();               // byte cinsinden boyut
    dosya.getContentType();        // MIME tipi (image/png ...)
    byte[] icerik = dosya.getBytes();         // bellekteki içerik
    // dosya.transferTo(hedefYol);  // doğrudan diske yaz
    ...
}

Birden çok dosya: @RequestParam("dosyalar") MultipartFile[] dosyalar veya List<MultipartFile>. Örnek 1 (./Ornek1.java) bir dosyayı yükler (bellek-içi depo) ve meta bilgisini döndürür.

Dosya indirme: ResponseEntity<byte[]>

İndirme için içeriği ve uygun başlıkları döndürürsün. Content-Disposition: attachment tarayıcıya "bunu indir" der:

@GetMapping("/indir/{ad}")
public ResponseEntity<byte[]> indir(@PathVariable String ad) {
    return ResponseEntity.ok()
        .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + ad + "\"")
        .contentType(MediaType.APPLICATION_OCTET_STREAM)
        .body(icerik);
}

Büyük dosyalar için byte[] yerine Resource (InputStreamResource/FileSystemResource) döndürmek belleği korur (akış halinde gönderir). Örnek 1 yüklenen dosyayı geri indirir ve içeriğin aynı olduğunu doğrular.

Yapılandırma

application.properties ile yükleme sınırları:

spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=20MB
spring.servlet.multipart.enabled=true

Üretim pratikleri ve güvenlik

  • Boyut sınırı koy: Aksi halde büyük dosyalar belleği/diski tüketir (DoS riski). Yukarıdaki ayarları kullan.
  • Dosya türünü doğrula: Yalnızca uzantıya güvenme; MIME/içerik doğrula. Çalıştırılabilir/ tehlikeli türleri reddet.
  • Dosya adını güvenli yap: İstemci adını doğrudan disk yoluna koyma (path traversal: ../../ saldırısı). Ad temizle veya rastgele üret.
  • Bellekte tutma: Büyük dosyaları getBytes() ile belleğe almak yerine transferTo/akış kullan veya doğrudan bir nesne deposuna (S3, MinIO) yükle.
  • Depolama: Üretimde dosyaları uygulama diskinde değil, nesne deposunda (S3/GCS) veya ayrı bir dosya servisinde sakla (ölçeklenme + dayanıklılık).

Özet

Spring Boot'ta dosya işlemeyi öğrendik: multipart/form-data ile yükleme (MultipartFile) ve Content-Disposition ile indirme (ResponseEntity<byte[]>/Resource; Örnek 1); boyut sınırı yapılandırması ve güvenlik (tür doğrulama, path traversal, nesne deposu) pratikleri. Sırada, uygulamayı farklı dillere uyarlamak: uluslararasılaştırma (i18n).

Kod Örnekleri(1)

Ornek1

ortam gerekir
Ornek1.java
1// Ornek1: Dosya yükleme (MultipartFile) ve indirme (ResponseEntity<byte[]>).
2// Çalıştırma: portal gömülü Tomcat ile başlatır, self-test çıktısını alır.
3package com.egitim.springboot.dosya;
4
5import org.springframework.boot.CommandLineRunner;
6import org.springframework.boot.SpringApplication;
7import org.springframework.boot.autoconfigure.SpringBootApplication;
8import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
9import org.springframework.context.annotation.Bean;
10import org.springframework.core.io.ByteArrayResource;
11import org.springframework.http.*;
12import org.springframework.util.LinkedMultiValueMap;
13import org.springframework.util.MultiValueMap;
14import org.springframework.web.bind.annotation.*;
15import org.springframework.web.client.RestClient;
16import org.springframework.web.multipart.MultipartFile;
17
18import java.nio.charset.StandardCharsets;
19import java.util.Map;
20import java.util.concurrent.ConcurrentHashMap;
21
22@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
23public class Ornek1 {
24
25    public static void main(String[] args) { SpringApplication.run(Ornek1.class, args); }
26
27    @RestController
28    @RequestMapping("/dosya")
29    static class DosyaController {
30        private final Map<String, byte[]> depo = new ConcurrentHashMap<>(); // bellek-içi (gerçekte disk/S3)
31
32        // YÜKLEME: multipart/form-data -> MultipartFile
33        @PostMapping("/yukle")
34        public Map<String, Object> yukle(@RequestParam("dosya") MultipartFile dosya) {
35            depo.put(dosya.getOriginalFilename(), getBytes(dosya));
36            return Map.of("ad", dosya.getOriginalFilename(),
37                    "boyut", dosya.getSize(),
38                    "tip", String.valueOf(dosya.getContentType()));
39        }
40
41        // İNDİRME: byte[] + Content-Disposition (tarayıcı "indir" diye algılar)
42        @GetMapping("/indir/{ad}")
43        public ResponseEntity<byte[]> indir(@PathVariable String ad) {
44            byte[] icerik = depo.get(ad);
45            if (icerik == null) return ResponseEntity.notFound().build();
46            return ResponseEntity.ok()
47                    .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + ad + "\"")
48                    .contentType(MediaType.APPLICATION_OCTET_STREAM)
49                    .body(icerik);
50        }
51
52        private static byte[] getBytes(MultipartFile f) {
53            try { return f.getBytes(); } catch (Exception e) { throw new RuntimeException(e); }
54        }
55    }
56
57    @Bean
58    CommandLineRunner selfTest() {
59        return args -> {
60            RestClient c = RestClient.create("http://localhost:8080");
61            System.out.println("\n========= DOSYA YÜKLE/İNDİR SELF-TEST =========");
62
63            // YÜKLE: multipart gövde oluştur (dosya adı + içerik).
64            byte[] icerik = "Merhaba, bu bir test dosyasıdır.".getBytes(StandardCharsets.UTF_8);
65            MultiValueMap<String, Object> govde = new LinkedMultiValueMap<>();
66            govde.add("dosya", new ByteArrayResource(icerik) {
67                @Override public String getFilename() { return "rapor.txt"; } // dosya adı şart
68            });
69            String yuklemeSonuc = c.post().uri("/dosya/yukle")
70                    .contentType(MediaType.MULTIPART_FORM_DATA)
71                    .body(govde).retrieve().body(String.class);
72            System.out.println("YÜKLE  -> " + yuklemeSonuc);
73
74            // İNDİR: yüklenen dosyayı geri al.
75            byte[] inen = c.get().uri("/dosya/indir/rapor.txt").retrieve().body(byte[].class);
76            System.out.println("İNDİR  -> içerik: '" + new String(inen, StandardCharsets.UTF_8) + "'");
77            System.out.println("İçerik aynı mı? " + java.util.Arrays.equals(icerik, inen));
78            System.out.println("===============================================");
79        };
80    }
81}
canlı çalıştırma için ortam gerekir

Bu örnek Spring / Spring Boot (Gradle) ortamı gerektirir; tek dosya olarak java Ornek1.java ile çalışmaz. Orijinal portal bunu gömülü Tomcat / Spring context ile koşuyordu. Beklenen davranış yukarıdaki anlatım ve kodda açıklanmıştır.