How to Return PDF Files from a Micronaut Endpoint
Micronaut is a JVM-based, lightweight framework for building scalable microservices. Returning a PDF file from a REST API endpoint is a common requirement in business applications—for reports, invoices, or receipts. Let us delve into understanding how to return a PDF 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 a PDF and return it as a downloadable file from a Micronaut controller. This includes setting up dependencies, writing services and injecting them into a controller.
2.1 Add dependencies (pom.xml)
Add the following dependencies to your build.gradle:
dependencies {
implementation("io.micronaut:micronaut-http-server-netty")
implementation("com.github.librepdf:openpdf:1.3.30") // OpenPDF library
}
2.2 Creating a Service Layer
The PdfService class is annotated with @Singleton, making it a managed bean in the Micronaut application context that can be injected wherever needed. It contains a single method, generatePdf(), which uses the OpenPDF library to generate a PDF file entirely in memory. Inside the method, a ByteArrayOutputStream is created to temporarily hold the PDF data. A Document object represents the PDF document, and PdfWriter.getInstance() is used to bind the document to the output stream. After opening the document, two paragraphs are added with custom messages, and then the document is closed, finalizing the content. Finally, the method returns the PDF content as a byte array using outputStream.toByteArray(), which can be returned directly as an HTTP response in the controller.
// src/main/java/com/jcg/service/PdfService.java
package com.jcg.service;
import jakarta.inject.Singleton;
import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.Paragraph;
import com.lowagie.text.pdf.PdfWriter;
import java.io.ByteArrayOutputStream;
@Singleton
public class PdfService {
public byte[] generatePdf() throws DocumentException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
Document document = new Document();
PdfWriter.getInstance(document, outputStream);
document.open();
document.add(new Paragraph("Hello, this is a PDF document from Micronaut!"));
document.add(new Paragraph("Generated using OpenPDF and served via Micronaut."));
document.close();
return outputStream.toByteArray();
}
}
2.3 Creating a Controller Class
The PdfController class is annotated with @Controller("/pdf"), defining a REST endpoint accessible at the /pdf path. It uses dependency injection via the @Inject annotation to obtain an instance of PdfService, which is responsible for generating the PDF content. The getPdf() method is mapped to an HTTP GET request and is configured to produce a response of type application/pdf using the @Get annotation with the produces attribute. Inside the method, it calls pdfService.generatePdf() to get the PDF content as a byte array, and constructs an HttpResponse with status 200 OK, the PDF as the response body, and sets the content type to MediaType.APPLICATION_PDF_TYPE. It also adds a Content-Disposition header to prompt file download with the name example.pdf. If an exception occurs, a server error response is returned using HttpResponse.serverError().
// src/main/java/com/jcg/controller/PdfController.java
package com.jcg.controller;
import com.example.service.PdfService;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import jakarta.inject.Inject;
@Controller("/pdf")
public class PdfController {
@Inject
PdfService pdfService;
@Get(produces = MediaType.APPLICATION_PDF)
public HttpResponse<byte[]> getPdf() {
try {
byte[] pdfBytes = pdfService.generatePdf();
return HttpResponse
.ok(pdfBytes)
.contentType(MediaType.APPLICATION_PDF_TYPE)
.header("Content-Disposition", "attachment; filename=\"example.pdf\"");
} catch (Exception e) {
return HttpResponse.serverError();
}
}
}
2.4 Run the Application
Once everything is in place, run the application using ./gradlew run. Navigate to http://localhost:8080/pdf in your browser and you’ll get a file named example.pdf downloaded with the following content:
Hello, this is a PDF document from Micronaut! Generated using OpenPDF and served via Micronaut.
3. Conclusion
By separating the PDF generation logic into a service class, we achieve a cleaner and more maintainable codebase. Using OpenPDF instead of iText allows you to work with an open-source and LGPL-licensed library, suitable for many commercial applications. This architecture provides a great starting point for building advanced PDF workflows with tables, images, dynamic data, and charts in Micronaut-based applications.



