DEV Community

IronSoftware
IronSoftware

Posted on

ClosedXML vs IronXL: The Hidden Cost of Open-Source .NET Excel Libraries

n open-source library rarely arrives as a decision. Someone needs a spreadsheet report on a Friday, EPPlus licensing looks like a conversation nobody wants to have, the Open XML SDK reads like plumbing, and ClosedXML with its XLWorkbook API gets it done. Sixteen years after its first commit it is still one of the most-installed Excel libraries in .NET, and that is exactly why it is worth looking at the way we look at any dependency before it turns load-bearing, which is to ask what it commits a team to over the next two years rather than what it does in the first afternoon. This piece walks ClosedXML the way we would walk it in an architecture review, and where it helps we contrast how IronXL handles the same work.

A quick disclosure before we go further. Our team at Iron Software builds IronXL, and we will point out where ClosedXML is the better buy.

What This Library Does Well

ClosedXML wraps the Open XML SDK in an object model built from workbooks, worksheets, cells, and ranges instead of raw XML parts and relationship IDs. A small report is short.

using ClosedXML.Excel;

// create a workbook and add a worksheet
using var workbook = new XLWorkbook();
var worksheet = workbook.Worksheets.Add("Sales");

// set the header and data cells, plus a SUM formula
worksheet.Cell("A1").Value = "Product";
worksheet.Cell("B1").Value = "Revenue";
worksheet.Cell("A2").Value = "Widgets";
worksheet.Cell("B2").Value = 42500;
worksheet.Cell("B3").Value = "Total";
worksheet.Cell("C3").FormulaA1 = "=SUM(B2:B2)";

// save the file
workbook.SaveAs("sales-report.xlsx");
Enter fullscreen mode Exit fullscreen mode

That writes a two-column sheet to sales-report.xlsx with no relationship IDs and no manual part management, only cells and values set directly.

That pattern is why it shows up in so many codebases. The license is MIT, and version 0.105.1 shipped July 25, 2026. ClosedXML calculates built-in Excel functions itself rather than shelling out to Excel. For an internal report that lives entirely in .xlsx and stays small, ClosedXML does that job, and on that narrow ground it is the better buy. What the core does not cover is the formats and the memory behavior most pipelines reach for next, which is where IronXL opens the file ClosedXML cannot.

Capability and Licensing Comparison

The differences that matter are easier to see side by side, so here is capability and licensing in one view.

Capability ClosedXML IronXL
License MIT Commercial, with vendor support and a maintained release line
Cost at scale Engineering time to work around the in-memory model at volume One license, predictable large-file handling
File formats .xlsx and .xlsm only .xls, .xlsx, .xlsm, .csv, .tsv, JSON, and XML from one API
Legacy .xls (binary) support Not supported Supported, WorkBook.Load opens binary .xls directly
Requires Excel or Interop installed No No, IronXL runs with no Excel installed
Cross-platform (Linux, Docker, Azure) Yes Yes, one build across Windows, Linux, macOS, and containers
Formula calculation Supported WorkBook.EvaluateAll, 150+ functions calculated in process
Large-workbook memory model Full in-memory DOM, no built-in streaming reader In-memory WorkBook model with predictable large-file handling
Public API stability Pre-1.0, own docs say the API is still not stable Versioned commercial release line, 2026.7.2
Support channel Community and GitHub issues Commercial support included with IronXL
Release cadence Active, 0.105.1 shipped July 2026 Active, 2026.7.2 shipped June 2026

Table 1. Capability and licensing, built from ClosedXML's README, docs site, and NuGet metadata against IronXL's published API.

Where This Library Falls Short

What follows is scope rather than defect, and scope is exactly what an architecture review is for, because the cost of an open-source library is rarely the license. It is the developer time spent building and maintaining what the library does not do, and the question of who owns that work two years from now when the person who chose it has moved to another team. Four gaps carry most of that cost.

  • No path to legacy .xls - ClosedXML reads and writes the Open XML formats only, .xlsx and .xlsm. A binary .xls file from an older ERP export, a decade-old template, or a partner feed that never left the legacy format will not open, and there is no fallback inside the library. The fix is a second dependency or a conversion step, and that glue is now something the team owns, tests, and carries through every upgrade. It is a small piece of code on the day it ships and a piece of tribal knowledge by the time someone new has to touch it. It also becomes part of the exit cost, because the day the team wants to change Excel libraries, the conversion shim is a second thing to unwind rather than one.
  • The whole workbook loads into memory at once - ClosedXML builds a full in-memory object model of the spreadsheet, with no streaming or SAX-style reader in the box. The project's own README publishes the numbers. Loading a million-row, ten-column text workbook takes roughly 800 MB of memory, and generating one from scratch runs close to 1.9 GB. A long-open GitHub issue, #86, tracks exactly this cost. On a memory-constrained container, or a batch job that processes several large exports at once, that is an out-of-memory risk to design around rather than a theoretical edge case. The architect version of the question is not how much memory one workbook needs, it is how many concurrent workbooks a container holds before it starts paging, because that number sets the throughput ceiling and it decides whether the answer to more volume is more replicas or a bigger box every month. Designing around it means either paying for that box or funding a rewrite that streams, and both are costs the MIT license never mentioned.
  • Sixteen years in, and still pre-1.0 - the first commit dates to May 2010 and the current release is 0.105.1. The documentation still tells you to read the release notes and the migration guide before every upgrade, because the public API, in its own words, is still not stable. For a team that does not want to re-test its formatting and formula code on every minor bump, that is a standing tax on maintenance, and it compounds. Multiply one careful upgrade a couple of times a year across a three-year horizon and the open-source library has quietly consumed a real slice of someone's calendar.
  • It is not thread-safe, and says so - ClosedXML is explicit that it is not thread-safe, and neither is the Open XML SDK underneath it. That is fine for one report at a time in a console app. It matters the moment the same code runs inside a web API or an Azure Function serving concurrent requests, where each request needs its own workbook instance and the team needs to know that rule before the first race condition shows up in production rather than after.

Each of these is survivable on its own. Added together they describe a library whose real price is paid in the code a team writes around it, and that price does not appear on any invoice. The way to make that price visible is to put a number on it. A conversion shim for one legacy format, a streaming path for the large workbooks, a documented per-request instancing rule for concurrency, and a re-test pass on every minor upgrade are not exotic asks, and none is more than a few days of work. Spread across a three-year horizon and a team that turns over even once, though, they add up to a quiet subsystem that someone has to understand well enough to change, and the person who understands it best is usually the one who has already left. That is the bus factor an architecture review is supposed to catch before it becomes a production incident, and it is the part of the total cost that a zero-dollar license line item hides most effectively.

Security and Maintenance

ClosedXML carries no CVEs of its own in the GitHub Advisory Database, NVD, or Snyk. What a security review has to weigh instead is the dependency surface, because a library inherits the advisories of everything it pulls in. ClosedXML's issue tracker shows the pattern. Issue #2491 references CVE-2024-43483 and CVE-2024-43484, the .NET runtime advisories covering a hash-flooding denial-of-service in System.IO.Packaging, System.Security.Cryptography.Cose, and Microsoft.Extensions.Caching.Memory. ClosedXML depends on System.IO.Packaging, so clearing that advisory meant waiting for a ClosedXML release that bumped the dependency. Issue #2099 shows the same shape earlier, flagging CVE-2018-8292 in System.Net.Http. The exposure is real while it is open, and the timeline for closing it runs on the maintainers' release cadence, not yours.

The maintenance picture is mixed. Releases do land, with 0.105.1 shipping July 25, 2026 and a push to the repository a day earlier. The issue tracker also sits at 468 open issues, a meaningful backlog for a community project with no dedicated support team behind it. Releases and a large open-issue count coexist, and both belong in the same review, because the question a security review answers is how fast a fix lands when a deadline is riding on it, not merely whether one exists, and behind a community backlog that timing is out of your hands.

Where IronXL Fits Instead

The teams that hit this in practice are the ones that meet the .xls wall, a legacy export ClosedXML will not open, or that need predictable memory on a large batch without redesigning around a full in-memory DOM. A library that covers the format range and calculates formulas in one pass is solving a different problem than ClosedXML was scoped for, and the code reads about the same.

using IronXL;

// Legacy .xls files that ClosedXML cannot open load the same way as .xlsx
WorkBook workBook = WorkBook.Load("quarterly-export.xls");
WorkSheet workSheet = workBook.DefaultWorkSheet;

// set a formula and calculate it in process, no Excel required
workSheet["C2"].Formula = "=SUM(B2:B50)";
workBook.EvaluateAll();

Console.WriteLine($"Quarterly total: {workSheet["C2"].FormattedCellValue}");

// save the result as xlsx
workBook.SaveAs("quarterly-export.xlsx");
Enter fullscreen mode Exit fullscreen mode

That opens a binary .xls, sets a formula, calculates it in process with WorkBook.EvaluateAll, and writes the result out as .xlsx, printing something like Quarterly total: 48250 on the way.

The trade is straightforward. IronXL opens and writes the older binary .xls alongside .xlsx, .xlsm, .csv, .tsv, JSON, and XML from one API, so there is no second library bolted on for a single file type, and it calculates formulas built in through WorkBook.EvaluateAll on the same WorkBook and WorkSheet model rather than leaving that to Excel. Commercial support comes with it, which is a real difference when a formula edge case is the thing standing between a team and a release. IronXL is licensed per deployment against ClosedXML's MIT terms, and that is the line the decision turns on.

There is a deployment shape behind this too, and it is the part the architect cares about most. Where a spreadsheet job belongs, a synchronous request, a background worker, or a scheduled batch, is driven partly by how the library behaves under load, and a full in-memory model with no streaming pushes the large jobs off the request thread and into a worker pool whose size is capped by memory rather than CPU. Covering the awkward formats and the calculation in one dependency does not remove that constraint on its own, but it removes the second and third libraries that would otherwise each need their own instancing rule, their own upgrade cadence, and their own line in the exit plan. Fewer moving parts is not a slogan here, it is one less thing to keep aligned across a build matrix and one less place for a new hire to guess wrong.

For a small internal report generator that only ever touches .xlsx, ClosedXML does the job. When the pipeline has to ingest whatever format a partner sends, or needs a support line instead of a GitHub queue when production breaks at 2am, the broader-format library is the more direct fit.

Wrapping Up

ClosedXML in 2026 is MIT-licensed and still shipping, and that is what the repository shows rather than anything we are spinning. What has not moved in sixteen years is the shape of it. Open XML formats only, a full in-memory model with no streaming path, an API its own maintainers still call not stable, and a fix timeline that runs on a community backlog. Stay where you are if the pipeline lives entirely in .xlsx, the workbooks stay in the tens of thousands of rows, and one report at a time is all the concurrency you need, and if you are willing to bet the next three years that none of those three ever changes, because ClosedXML will not move to meet you when one of them does. Move to IronXL if you have to open a legacy .xls, if predictable memory on large files is a requirement rather than a hope, or if you need a support channel behind the library when a release is on the line. The longer format-by-format comparison walks the same ground in more depth.

So where does your project land right now, comfortably inside .xlsx and small-to-mid workbooks, or already fighting a legacy format or a memory ceiling in production?

If you are weighing the move, the quickest test is to run one of your own awkward files through IronXL and watch what it does. Which wall did you hit first, the format or the memory ceiling? Tell us in the comments.

ClosedXML is the property of its respective owners, and our team has no affiliation with the project. The facts above are drawn from its public README, documentation, and CVE records at the time of writing. If a detail has moved since, correct us in the responses.

Top comments (0)