Jmix.gridexport.export-all-batch-size does not work?

I’m using the export add-on, specifically the grid export action, and I’d like to limit the number of records a user can download (since I have more than 600,000 records and the page can’t handle that volume).I’ve seen there’s a property called jmix.gridexport.export-all-batch-size , which is supposed to limit the number of records to download. Is that what it’s for? If so, it’s not working, because I set the value to 10 in my application.properties file, but it still downloads a larger amount. And if it’s not intended for that, what is it used for?Thanks in advance for your help. Best regards!

P.S.: I’m using Jmix 2.5.1.

Greetings

Here’s a quick proof from a clean Jmix 2.5.1 project showing that jmix.gridexport.export-all-batch-size indeed controls the batch size:

  1. Clean project

  2. Create “NewEntity”

  3. I create them 10000 rows

  4. Add logging level

    # 'debug' level logs SQL generated by EclipseLink ORM
    logging.level.eclipselink.logging.sql=DEBUG
    spring.jpa.show-sql=true
    
    # 'debug' level logs data store operations
    logging.level.io.jmix.core.datastore=DEBUG
    
  5. Tests:

    Default (no override, batch=1000)

    2025‑04‑17T22:24:44.348+03:00 DEBUG … AbstractDataStore : … maxResults=1000 …
    SELECT LIMIT ? ? … bind => [0, 1000]
    

    (repeated in subsequent batches of 1 000 rows each - 10x 1000, in console 10 queries)

    After setting
    jmix.gridexport.export-all-batch-size=10000

    2025‑04‑17T22:26:41.658+03:00 DEBUG … AbstractDataStore : … maxResults=10000 …
    SELECT LIMIT ? ? … bind => [0, 10000]
    

    (and then continues in batches of 10 000, 1 query and 10000 rows per query)


What’s really happening

  • export-all-batch-size only changes the per‑call maxResults, i.e. how many rows are fetched each time Jmix loops.
  • It does not impose a cap on the total number of records exported. Jmix will keep fetching (in your logs, 60+ batches when you had 600 000 rows) until there’s nothing left.

Recommendation for Large-Scale Exports (60 000+ records)

When you’re dealing with tens or hundreds of thousands of rows, it’s safer—and kinder to your application’s memory—to take control of the export process yourself:

  1. Use an async service or dedicated thread‑pool

    • Define a Spring @Service that exposes an async method (or directly use a ExecutorService via Executors.newFixedThreadPool(...)).
    • Invocations return a CompletableFuture<Path> (or Future<Path>) representing the CSV/Excel file you’re generating.
  2. Stream in batches, write per‑batch

    • In your export task, loop with increasing offsets (or key‑based paging) and load only, say, 1 000–5 000 rows per batch: (pseudo-example)
      long lastId = 0;
      Path temp = Files.createTempFile("export-", ".csv");
      try (BufferedWriter writer = Files.newBufferedWriter(temp)) {
          while (true) {
              List<MyEntity> batch = dataManager.load(MyEntity.class)
                  .query("select e from MyEntity e where e.id > :lastId order by e.id")
                  .parameter("lastId", lastId)
                  .maxResults(batchSize)
                  .list();
              if (batch.isEmpty()) break;
              for (MyEntity e : batch) {
                  writer.write(e.toCsvLine());
                  writer.newLine();
                  lastId = e.getId();
              }
              writer.flush();  // push to disk, free buffers
          }
      }
      return temp;
      
    • Flushing after each batch (or using unbuffered writes) prevents the JVM from accumulating the entire file in memory.
  3. Notify the user when ready

    • Immediately return control to the UI: “Your export has started and will be available shortly.”
    • When the Future completes, send an in‑app notification or email with a download link to the generated file.

Best regards,
Dmitry

Thank you so much for your excellent explanation — I really appreciate it.

So, in conclusion:
• You’re suggesting that I shouldn’t use the Grid Export Action add-on to handle this task (which I was using), and instead create my own export dialog and manually generate the CSV.
• So, there’s no way to intercept the Grid Export Action event to check how many records the user wants to export and decide whether to limit it or not?
• If there is a way to intercept the “export all” action, I could use that to trigger my own CSV generation logic.
• In the example you showed, where the CSV is generated via a service — should this be used from a custom REST endpoint, rather than a View and Controller?

I’ll be trying out all the options. Thank you very much again!

Hi,

You’re suggesting that I shouldn’t use the Grid Export Action add‑on to handle this task (which I was using), and instead create my own export dialog and manually generate the CSV.

Yes. With data sets this large you’ll get better results if you implement your own exporter and tune it for performance and memory usage.

  • Run the root task asynchronously, and make each sub‑task asynchronous as well.
  • Pick a batch size that gives the best trade‑off between speed and RAM—about 1 000 – 5 000 rows is usually fine.
  • Let every batch stream its chunk straight into the same file.

Typical optimisations: start one async thread that parallelises the queries; if the total record count is under ± 60 000 you can process it in a single pass, otherwise switch to a “super‑batch” pattern. After each batch is loaded, write it synchronously to disk. The exact strategy is up to you—decide whether you prefer the fastest download or the lowest resource usage.

You can also plug in a custom exporter and override the default logic:
https://docs.jmix.io/jmix/grid-export/exporters.html

So, there’s no way to intercept the Grid Export Action event to check how many records the user wants to export and decide whether to limit it or not?

Yes, you can. Provide your own exported bean or create a new action that extends and overrides the existing one.

If there is a way to intercept the “export all” action, I could use that to trigger my own CSV generation logic.

Exactly—that’s the approach described above.

In the example you showed, where the CSV is generated via a service — should this be used from a custom REST endpoint, rather than a View and Controller?

If you want the logic to be reusable, then yes—put it in your own @Service and call that service from the exporter (or expose it via REST if needed).

For reference, the source of the export add‑on is quite small and easy to follow:

Speed vs. complexity

Level How it works When to choose
1 — Maximum speed (parallel fetch, ordered write) • In the async task, spawn several workers that fetch different batches in parallel.
• Each worker puts its result into a concurrent map keyed by batch index.
• A single writer thread pulls the batches 0, 1, 2… from the map in order and appends them to the file, so the final CSV stays sequential.
→ Fastest, but the code is the most complex (synchronisation, back‑pressure, error handling).
Huge exports where end‑‑to‑‑end time really matters and you can invest in a more elaborate implementation.
2 — Balanced (one async task, sequential batches) • One background thread.
• Inside it, fetch batches of 1 000 – 5 000 rows sequentially and write each batch straight to the file (use BufferedWriter, CSVPrinter, etc.).
• Very little RAM footprint, simpler code than level 1, still much lighter than the default add‑on.
Most business scenarios: 100 k – 1 M rows, need to keep the server healthy but don’t require the absolute minimum runtime.
3 — Quick & easy (built‑in exporter) Just use the stock Jmix Export action. For data sets up to ≈ 100 k rows it performs well enough and you get progress UI and translations for free. Admin‑only exports, occasional downloads, prototypes. Minimal effort.

Conclusions

If you only run this once in a while and the file is under ~100 k lines, stick with the built‑in exporter.
If you need better memory usage and predictable times, implement level 2.
If users are waiting on a 600 k+ export every day, invest in level 1.

Choose the simplest option that meets your performance target — no point in over‑engineering if a buffered single‑thread export already gets the job done.