Software Development

How to Return a ZIP File in a Micronaut Controller

Serving downloadable ZIP files is a common requirement in web applications—whether to bundle logs, documents, or export files. Let us delve into understanding how to return a ZIP file in a Micronaut controller.

1. What is Micronaut?

Micronaut is a modern, JVM-based, full-stack framework specifically designed for building modular, high-performance microservices, serverless applications, and cloud-native systems. It offers first-class support for Java, Kotlin, and Groovy, making it a flexible choice for JVM developers.

Unlike traditional Java frameworks that rely heavily on runtime reflection and proxies (such as Spring), Micronaut uses compile-time annotation processing for dependency injection, AOP (Aspect-Oriented Programming), and configuration. This unique approach drastically reduces memory usage and startup time, making it ideal for microservices and serverless environments like AWS Lambda or Google Cloud Functions.

Micronaut also integrates seamlessly with GraalVM to enable ahead-of-time (AOT) compilation into native executables, further improving performance and resource efficiency.

Key features of Micronaut include:

  • Compile-time dependency injection: No reflection or runtime proxies, leading to better performance and smaller memory footprint.
  • Fast startup time and low memory usage: Ideal for microservices and containerized environments.
  • Seamless integration with GraalVM: Supports building native images for ultra-fast execution and minimal memory use.
  • Reactive and non-blocking: Includes built-in support for reactive programming using RxJava, Project Reactor, etc.
  • Built-in HTTP server/client: Lightweight Netty-based web server and declarative HTTP clients with easy configuration.
  • Cloud-native readiness: Support for service discovery, distributed tracing, configuration management, and more.
  • Modular architecture: Encourages separation of concerns and better maintainability.

2. Code Example and Explanation

In this section, we’ll walk through how to generate and return a ZIP file dynamically from a Micronaut controller. This includes setting up dependencies, writing code logic, and injecting it into a controller.

2.1 Add dependencies (pom.xml)

First, we set up the required dependencies in our pom.xml.

<dependencies>  
  <!-- Enables Micronaut's embedded HTTP server using Netty -->
  <dependency>
    <groupId>io.micronaut</groupId>
    <artifactId>micronaut-http-server-netty</artifactId>
  </dependency>
  <!-- Provides core runtime support for running Micronaut applications -->
  <dependency>
    <groupId>io.micronaut</groupId>
    <artifactId>micronaut-runtime</artifactId>
  </dependency>
  <!-- Required for dependency injection and bean management in Micronaut -->
  <dependency>
    <groupId>io.micronaut</groupId>
    <artifactId>micronaut-inject</artifactId>
  </dependency>
</dependencies>

2.2 Java Controller Code

Create a controller class named ZipDownloadController.java:

package com.example;

import io.micronaut.http.HttpResponse;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Produces;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

@Controller("/download")  // Defines a controller with the base URL path "/download"
public class ZipDownloadController {

    @Get("/zip")  // Maps GET requests to "/download/zip"
    @Produces(MediaType.APPLICATION_OCTET_STREAM)  // Indicates the response will be a binary stream (e.g., file download)
    public HttpResponse<byte[]> downloadZip() throws IOException {
        // Creates an in-memory byte stream to hold the ZIP content
        ByteArrayOutputStream byteStream = new ByteArrayOutputStream();

        try (ZipOutputStream zipOut = new ZipOutputStream(byteStream)) {
            // Add the first file named "hello.txt" with simple text content
            zipOut.putNextEntry(new ZipEntry("hello.txt"));
            zipOut.write("Hello from Micronaut!".getBytes());
            zipOut.closeEntry();

            // Add the second file named "data/info.txt" inside a folder with text content
            zipOut.putNextEntry(new ZipEntry("data/info.txt"));
            zipOut.write("This is an info file inside a folder.".getBytes());
            zipOut.closeEntry();
        }

        // Convert the completed ZIP content to a byte array
        byte[] zipBytes = byteStream.toByteArray();

        // Return the ZIP file in the HTTP response with appropriate headers for file download
        return HttpResponse.ok(zipBytes)
                .header("Content-Disposition", "attachment; filename=\"sample.zip\"")  // Triggers download with filename
                .contentType(MediaType.APPLICATION_OCTET_STREAM_TYPE)  // Sets MIME type to binary stream
                .contentLength(zipBytes.length);  // Sets the content length
    }
}

2.2.1 Code Explanation

This Micronaut controller, mapped to the /download path, defines a GET endpoint /zip that returns a ZIP file as a downloadable byte array. When accessed, the downloadZip() method creates an in-memory ZIP archive using ByteArrayOutputStream and ZipOutputStream. It adds two text files to the archive: hello.txt containing “Hello from Micronaut!” and data/info.txt placed inside a subfolder, containing “This is an info file inside a folder.” After writing both files, the ZIP content is returned as an HTTP response with headers to prompt a file download named sample.zip, using the application/octet-stream content type and the appropriate content length.

2.3 Expected Output

When you access http://localhost:8080/download/zip, your browser will download a file named sample.zip. Inside the ZIP file, you will find:

  • hello.txt – containing the text: Hello from Micronaut!
  • data/info.txt – containing: This is an info file inside a folder.

3. Conclusion

In this article, we demonstrated how to serve a dynamically created ZIP file from a Micronaut controller. This is particularly useful for downloading endpoints that aggregate files on the fly. Micronaut’s lightweight nature makes it an excellent choice for such efficient server-side operations.

Yatin Batra

An experience full-stack engineer well versed with Core Java, Spring/Springboot, MVC, Security, AOP, Frontend (Angular & React), and cloud technologies (such as AWS, GCP, Jenkins, Docker, K8).
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button