XLS Versus XLSX Versus XLSB and Which to Hand Over
Compare the XLS, XLSX and XLSB Excel formats on limits, size, macros and library support, detect which one a file really is, and pick one to hand over.

Three file extensions cover almost every Excel file you will be given, and they are not interchangeable. A system that exports .xls, a colleague who saves .xlsb to make a heavy file smaller, and a script that expects .xlsx will each cause a different error, and the error messages rarely mention the format. This post compares the three on the properties that matter for moving data, shows how to tell what a file really is regardless of its name, and ends with a recommendation for what to send. It is part of the Excel cluster that starts with what is inside an Excel file.
The three formats
XLS is the Excel 97 to 2003 format, a binary container (OLE2, also called Compound File Binary) holding a stream of records. It has 65,536 rows and 256 columns per sheet, and its limits on styles and colors are far lower than modern Excel. It cannot be read by unzipping. Many old systems, report generators and instruments still write it because the library they use was written in 1999.
XLSX is the Office Open XML format introduced with Excel 2007: a zip of XML parts, 1,048,576 rows and 16,384 columns, no macros. It is an open standard (ECMA-376 and ISO/IEC 29500), and you can read it with an archive tool and a text editor. It is the format almost every library targets first.
XLSB is the Excel binary workbook: also a zip package, but the parts are binary records and not XML. It stores the same data as XLSX with the same limits, usually in a smaller file that Excel loads faster, since parsing binary records is cheaper than parsing XML. It can hold macros. Its drawback is support: Excel and a few libraries read it, and many tools do not.
There is a fourth member of the family worth naming. XLSM is the XLSX layout with macros enabled. It is XML, a zip, and as readable as XLSX, but a vbaProject.bin part carries the code, and many mail filters block it.
| Property | XLS | XLSX | XLSB |
|---|---|---|---|
| Container | OLE2 binary | Zip of XML | Zip of binary parts |
| Rows by columns | 65,536 by 256 | 1,048,576 by 16,384 | 1,048,576 by 16,384 |
| Macros | Yes | No (use XLSM) | Yes |
| Typical size | Larger than XLSX for the same data | Baseline | Often the smallest |
| Readable without Excel | Hard | Easy | Hard |
| Library support | xlrd, calamine | Nearly everything | pyxlsb, calamine |
How the libraries treat each one
Support differs more than the table suggests, and this is where handovers fail. In Python:
openpyxl reads and writes XLSX and XLSM. It does not read XLS or XLSB, and raises an error for them.
xlrd reads XLS. Version 2.0 and later dropped XLSX support, so it is only for the old format.
pyxlsb reads XLSB, with fewer features than openpyxl has for XLSX.
python-calamine is a Rust-based reader that opens XLS, XLSX, XLSB and ODS through one interface. pandas can use it as
engine="calamine"in recent versions (choosing between openpyxl, xlsxwriter and calamine).pandas picks an engine from the extension when it can, and fails with an instruction to install one when it cannot (fixing the missing optional dependency openpyxl error).
DuckDB's Excel extension reads XLSX. The database import wizards of SQL Server and MySQL Workbench have their own rules, and some need a driver for the format: SQL Server's tooling relies on the Access Database Engine (ACE) provider for XLSX and XLSB.
In practice, if your pipeline is written with openpyxl and someone sends an .xlsb, the pipeline stops. Converting the file once in Excel (Save As, XLSX) is the fastest fix, and the permanent fix is to agree on a handover format.
Do not trust the extension
Files get misnamed constantly. A web application "exports to Excel" by returning an HTML table with an .xls extension, because Excel opens it. Another writes a tab-separated text file and calls it .xls. A script renames a CSV. Excel opens all of these, with a warning that the file format and extension do not match, and every library fails differently. Check the first bytes of the file:
import sys, zipfile
SIGNATURES = {
b"\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1": "OLE2 binary (real XLS, or an old DOC or PPT)",
b"PK\x03\x04": "zip package (XLSX, XLSM, XLSB, ODS or another zip)",
}
path = sys.argv[1]
with open(path, "rb") as f:
head = f.read(8)
kind = next((v for k, v in SIGNATURES.items() if head.startswith(k)), None)
if kind is None:
text = head.lstrip(b"\xef\xbb\xbf \r\n\t")[:1]
kind = "HTML or XML text" if text == b"<" else "plain text (CSV or TSV?)"
print(path, "->", kind)
if head.startswith(b"PK"):
names = zipfile.ZipFile(path).namelist()
if "xl/workbook.bin" in names:
print(" workbook.bin present: XLSB")
elif "xl/workbook.xml" in names:
macros = "xl/vbaProject.bin" in names
print(" workbook.xml present: XLSX" + (" with macros (XLSM)" if macros else ""))The eight-byte OLE2 signature D0 CF 11 E0 A1 B1 1A E1 marks a binary XLS. Every zip begins with PK. Inside a zip, xl/workbook.xml marks the XML flavor and xl/workbook.bin marks XLSB. A file that starts with < is HTML or XML, and one that starts with something else printable is probably delimited text. For a web "export" renamed to .xls, pandas can read it with read_html, not read_excel, and the quicker fix is to ask the source for a CSV.
Which to hand over
The choice depends on who reads the file.
A person with Excel. Send XLSX. Every version since 2007 opens it, Google Sheets and LibreOffice open it, and macros are not an issue. Choose XLSB only for an internal file that is slow to open, where everyone who touches it uses desktop Excel, and keep an XLSX copy for anyone else.
A program written in any language. Send CSV if the data is one table, and see Excel versus CSV for sharing data with other systems. If styles, several sheets or formulas matter, send XLSX with the data on a single sheet, a header in row 1 and identifiers stored as text.
An archive. XLSX, with the data also exported as CSV. The XML layout is documented and readable in the long term, which cannot be said for the binary formats.
A legacy system that demands XLS. Send XLS, keep it under 65,536 rows, and make sure no more than 256 columns exist. Write it with a library, not by renaming a CSV, and test the file in the receiving system. Treat it as a bridge that you plan to retire.
Never hand over XLSM unless the receiver asked for the macros. A file with macros is blocked by many filters and treated with suspicion by good habit.
Converting between them
Excel's Save As converts one to another and is the most faithful option for a single file. For many files, LibreOffice can run without a window:
soffice --headless --convert-to xlsx --outdir converted old/*.xlsThe command reads every .xls file in old and writes a .xlsx file for each into converted. Conversion is not lossless. Formatting that has no equivalent is approximated, charts can change, and formulas are recalculated by the converting program, which may differ from Excel in edge cases. Compare totals before and after, as in validating row counts after an Excel import. With python-calamine you can skip the conversion and read the old file directly, which avoids the question for data-only use.
Be careful about the direction. Converting XLSX to XLS to satisfy an old system silently drops every row after 65,536 in some tools, and fails in others. Check the row count first.
Size and speed in practice
XLSB is often described as 50 to 70 percent smaller than XLSX and several times faster to open. Claims like these come from vendor guidance and depend heavily on the content: a workbook that is mostly text compresses well in either, and one with large formula chains gains the most from the binary layout. I have not measured them here. If you are considering XLSB for speed, convert a representative file and time the open in Excel and the load in your pipeline. XLSX already compresses with zip, so the gap is smaller than the gap between XLS and XLSX was.
The reverse matters more for data engineering: XLSX parsing is slow because XML parsing is slow, and a million-row file takes noticeable time in any library. A fast reader helps (reading Excel files faster with python calamine), and so does reading in a mode that streams instead of loading everything (reading large Excel files in read-only mode).
When none of the three Excel formats is the right handover
The data is more than a million rows. All three formats stop at 1,048,576 rows. Use CSV, a database or a columnar file such as Parquet (Excel row and column limits).
The receiver is a machine and the data has types. A spreadsheet carries only numbers, strings, booleans and dates-as-numbers. Typed formats such as Parquet or Avro, or JSON with a schema, say more.
You need version control. Binary and zipped formats diff poorly. Keep the source as CSV in the repository and generate workbooks from it.
The file is a template with logic. Formulas and macros that the receiver must keep working are a reason for XLSX or XLSM, but also a reason to ask whether the logic belongs in code.
The structure of the package formats is described in ECMA-376, and the binary workbook format in Microsoft's MS-XLSB specification. For a quick look at a small table as JSON, use the CSV to JSON converter.


