Backend Development 16 min read

Concept‑Download: A Spring Library for Annotation‑Driven File Download

Concept‑Download is a Spring library that lets developers replace verbose, multi‑step file‑download code with a single @Download annotation, automatically handling source loading, optional ZIP compression, and response writing for both MVC and WebFlux via a modular reactive handler chain.

Java Tech Enthusiast
Java Tech Enthusiast
Java Tech Enthusiast
Concept‑Download: A Spring Library for Annotation‑Driven File Download

This article introduces a Java library that simplifies file‑download implementations in Spring MVC and WebFlux by using a single annotation.

Typical download scenario : exporting QR‑code images of devices as a zip file. The usual implementation requires dozens of steps – querying devices, downloading each image, checking cache, compressing, and writing the response – resulting in hundreds of lines of code.

Goal : provide a declarative way where the controller only returns the data to be downloaded (e.g., a list of objects, a file path, a URL) and the library handles the rest.

Usage example :

@Download(source = "classpath:/download/README.txt")
@GetMapping("/classpath")
public void classpath() { }

or for a zip of device QR codes:

@Download(filename = "二维码.zip")
@GetMapping("/download")
public List
download() {
    return deviceService.all();
}

public class Device {
    private String name;
    @SourceObject
    private String qrCodeUrl;
    @SourceName
    public String getQrCodeName() { return name + ".png"; }
}

Design ideas

The library is built on reactive programming but works for both WebMVC and WebFlux. It abstracts the download process into a chain of DownloadHandler steps, each operating on a shared DownloadContext .

Key interfaces :

/** Download handler */
public interface DownloadHandler extends OrderProvider {
    Mono
handle(DownloadContext context, DownloadHandlerChain chain);
}

public interface DownloadHandlerChain {
    Mono
next(DownloadContext context);
}

Source abstraction : any downloadable object is wrapped as a Source . Implementations such as FileSource and HttpSource are created by SourceFactory implementations.

public interface SourceFactory extends OrderProvider {
    boolean support(Object source, DownloadContext context);
    Source create(Object source, DownloadContext context);
}

Loading : SourceLoader can load resources concurrently (thread‑pool, coroutine, etc.).

public interface SourceLoader {
    Mono
load(Source source, DownloadContext context);
}

Compression : SourceCompressor defines the compression format (e.g., zip) and performs the actual archiving.

public interface SourceCompressor extends OrderProvider {
    String getFormat();
    default boolean support(String format, DownloadContext ctx) { return format.equalsIgnoreCase(getFormat()); }
    Compression compress(Source source, DownloadWriter writer, DownloadContext ctx);
}

Response writing : The library abstracts the HTTP response with DownloadResponse to support both HttpServletResponse (WebMVC) and ServerHttpResponse (WebFlux). An example for WebFlux:

@Getter
public class ReactiveDownloadResponse implements DownloadResponse {
    private final ServerHttpResponse response;
    private OutputStream os;
    private Mono
mono;

    public ReactiveDownloadResponse(ServerHttpResponse response) { this.response = response; }

    @Override
    public Mono
write(Consumer
consumer) {
        if (os == null) {
            mono = response.writeWith(Flux.create(sink -> {
                try {
                    os = new FluxSinkOutputStream(sink, response);
                    consumer.accept(os);
                } catch (Throwable e) { sink.error(e); }
            }));
        } else {
            consumer.accept(os);
        }
        return mono;
    }

    @SneakyThrows
    @Override
    public void flush() { if (os != null) os.flush(); }

    @AllArgsConstructor
    public static class FluxSinkOutputStream extends OutputStream {
        private FluxSink
sink;
        private ServerHttpResponse response;
        @Override public void write(byte[] b) { writeSink(b); }
        @Override public void write(byte[] b, int off, int len) {
            byte[] bytes = new byte[len];
            System.arraycopy(b, off, bytes, 0, len);
            writeSink(bytes);
        }
        @Override public void write(int b) { writeSink((byte) b); }
        @Override public void flush() { sink.complete(); }
        public void writeSink(byte... bytes) {
            DataBuffer buffer = response.bufferFactory().wrap(bytes);
            sink.next(buffer);
            DataBufferUtils.release(buffer);
        }
    }
}

Event system : To keep the architecture extensible, the library publishes DownloadEvent s via DownloadEventPublisher and allows listeners to react to various stages (initialization, loading, compression, writing, termination).

Logging : Several listeners produce detailed logs for each step, progress updates, and total time spent, which helped uncover many bugs during development.

Other pitfalls : In WebFlux the response writing returns Mono.empty() , so downstream handlers are not invoked automatically. The author solved this by moving context cleanup to doAfterTerminate .

Overall, the library demonstrates how to combine annotations, reactive streams, and a modular handler chain to turn a complex download workflow into a few declarative lines of code.

JavaSpringAnnotationsfile downloadReactive
Java Tech Enthusiast
Written by

Java Tech Enthusiast

Sharing computer programming language knowledge, focusing on Java fundamentals, data structures, related tools, Spring Cloud, IntelliJ IDEA... Book giveaways, red‑packet rewards and other perks await!

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.