Streaming Large-Scale Data Export in SpringBoot Using JPA and MyBatis to Avoid OOM

The article explains how to prevent OutOfMemoryError when exporting massive MySQL datasets by streaming records with JPA or MyBatis, writing them directly to CSV files, and demonstrates significant memory savings compared to traditional batch export methods.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Streaming Large-Scale Data Export in SpringBoot Using JPA and MyBatis to Avoid OOM

Dynamic data export is a common requirement; loading large MySQL result sets into memory for Excel/CSV generation can cause OutOfMemoryError.

To avoid OOM, the article proposes streaming data from MySQL using JPA or MyBatis, writing each record directly to a CSV file and detaching entities after use.

JPA implementation uses a repository method returning Stream<Todo> with @QueryHints(fetchSize = Integer.MIN_VALUE) and @Transactional(readOnly = true); the controller writes each line to the HTTP response and detaches the entity to free memory.

@QueryHints(value = @QueryHint(name = HINT_FETCH_SIZE, value = "" + Integer.MIN_VALUE))
@Query("select t from Todo t")
Stream<Todo> streamAll();
@RequestMapping(value = "/todos.csv", method = RequestMethod.GET)
@Transactional(readOnly = true)
public void exportTodosCSV(HttpServletResponse response) {
    response.addHeader("Content-Type", "application/csv");
    response.addHeader("Content-Disposition", "attachment; filename=todos.csv");
    response.setCharacterEncoding("UTF-8");
    try (Stream<Todo> todoStream = todoRepository.streamAll()) {
        PrintWriter out = response.getWriter();
        todoStream.forEach(rethrowConsumer(todo -> {
            String line = todoToCSV(todo);
            out.write(line);
            out.write("
");
            entityManager.detach(todo);
        }));
        out.flush();
    } catch (IOException e) {
        log.info("Exception occurred " + e.getMessage(), e);
        throw new RuntimeException("Exception occurred while exporting results", e);
    }
}

MyBatis implementation defines a custom ResultHandler and sets fetchSize="-2147483648" in the mapper XML to stream rows without loading them all into memory.

public class DownloadProcessor {
    private final HttpServletResponse response;
    public DownloadProcessor(HttpServletResponse response) {
        this.response = response;
        String fileName = System.currentTimeMillis() + ".csv";
        this.response.addHeader("Content-Type", "application/csv");
        this.response.addHeader("Content-Disposition", "attachment; filename=" + fileName);
        this.response.setCharacterEncoding("UTF-8");
    }
    public <E> void processData(E record) {
        try {
            response.getWriter().write(record.toString());
            response.getWriter().write("
");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
public class CustomResultHandler implements ResultHandler {
    private final DownloadProcessor downloadProcessor;
    public CustomResultHandler(DownloadProcessor downloadProcessor) {
        this.downloadProcessor = downloadProcessor;
    }
    @Override
    public void handleResult(ResultContext resultContext) {
        Authors authors = (Authors) resultContext.getResultObject();
        downloadProcessor.processData(authors);
    }
}
public interface AuthorsMapper {
    List<Authors> selectByExample(AuthorsExample example);
    List<Authors> streamByExample(AuthorsExample example); // fetchSize="-2147483648" in XML
}

The service provides streamDownload (streaming) and traditionDownload (batch) methods; memory‑usage tests show streaming reduces peak memory from about 2.5 GB to roughly 500 MB while producing identical CSV files.

Test data can be generated via stored procedures or downloaded from a provided link, and the article also shows how to monitor memory usage with jconsole.exe.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

StreamingmysqlMyBatisSpringBootCSVjpaDataExport
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

0 followers
Reader feedback

How this landed with the community

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.