Special thanks to Virginia Arroyo, Oracle Cloud Migration Director, for her review of this article.
There is a quiet assumption baked into almost every modern database conversation, and it goes something like this: everything is Unicode now, so encoding is a solved problem. For most greenfield systems, that is more or less true. But walk into the data centre of a European bank, an insurer, or a government agency, and you will find a different reality: rows of cabinets with workloads that were written before most of today’s developers were born, still speaking a character encoding that most of them have never had to think about.
That encoding is EBCDIC – the Extended Binary Coded Decimal Interchange Code – and it has been quietly keeping the world’s financial plumbing running for sixty years. The estimates are staggering: somewhere around 220 billion lines of COBOL are still in active production, roughly 95 percent of ATM swipes and 80 percent of in-person banking transactions still run on COBOL, and many of those workloads are forty or fifty years old. Mainframes are not a legacy footnote. They are the operational backbone of the European financial sector, and EBCDIC is the alphabet they think in.
This is why Oracle’s latest database release matters more than the average feature announcement. With Oracle AI Database 26ai, the database team has quietly shipped a set of capabilities aimed at one of the oldest, most persistent headaches in mainframe re-platforming: EBCDIC compatibility. It is a genuine, targeted repair of a fault that has broken more than one migration project that otherwise looked finished.
The vocabulary: character sets and collations
Before the details, two terms need a clear definition, because the whole story hangs on keeping them straight.
A character set is an encoding: it decides which byte values exist and which character each one stands for. It answers “what does this byte mean?” When data crosses from a mainframe into an Oracle database, the character set governs that translation.
A collation is an ordering rule: it decides how two character values compare – which one sorts before the other. It answers “does 7 come before A?” and drives everything that depends on comparison: WHERE predicates, ORDER BY clauses, and the order in which an index walks its keys.
They are related but independent. A correct mainframe migration has to get both right: convert each character accurately (character set) and preserve the byte-level ordering that legacy SQL depends on (collation).
Why EBCDIC refuses to die
Before we look at what Oracle changed, it helps to understand why this problem exists at all, because the answer is not “nobody has simply gotten around to it yet.” The key is that moving EBCDIC data onto an ASCII-based database is not a single task. It is two separate tasks that are easy to confuse, and a migration that fixes only one of them can still be wrong.
The first task is character conversion. On the mainframe, every character is stored as a byte value defined by the EBCDIC code page. When that byte travels to an Oracle database that runs on an ASCII-based encoding, the database has to translate it into the correct Unicode character. If that translation table is wrong, you get corrupted data on the way across: accented letters come out as garbage, control codes collapse into the wrong characters, and names or addresses are mangled. This is the half that is easy to catch, because a wrong conversion usually shows up as visible garbage in your very first test load.
The second task is ordering, and it is the dangerous one precisely because it is invisible. A database compares and sorts character values by their underlying byte values. The EBCDIC code page and the ASCII code page assign different byte values to the same characters, so the identical set of values lines up in a different order depending on which code page you assume. A mainframe application that has relied on EBCDIC ordering for decades can contain WHERE clauses, index assumptions, and sort expectations that quietly break the moment the data lands on an ASCII-based database – even though every single character still looks correct on the screen.
So a mainframe re-platform has to get two things right at the same time: convert each character accurately, and preserve the byte-level ordering that the legacy SQL depends on. Get the conversion wrong and the data is visibly broken, which is annoying but detectable. Get the ordering wrong and the data looks perfect while every query that ever relied on EBCDIC collation silently returns the wrong rows. That second failure mode is the nightmare scenario, because it survives code review and passes every test that only checks whether the characters render correctly.
Oracle describes the feature this way: accurate character encoding conversion on one side, and preservation of EBCDIC binary ordering on the other.
How 19c tried to get there: linguistic sorting and linguistic indexes
Before 26ai shipped the emulated binary collations, a DBA who needed a column to sort by something other than the code page’s own byte order had exactly one tool: linguistic collation, and the way to keep it fast was a linguistic index. It is worth understanding both, because the emulated binary collation is best read as a direct answer to the limitation of the linguistic one.
A linguistic collation is any collation that orders characters by the rules of a language rather than by their byte values. Where binary collation says “sort by the number stored in the character set,” a linguistic collation says “sort the way a dictionary for this language would.” Internally it does this by replacing each character with a numeric sort value and comparing those values instead of the raw bytes. For a single-language (monolingual) sort that value is a pair – a major value and a minor value; for a multilingual or Unicode Collation Algorithm (UCA) sort it is a stack of primary, secondary and tertiary levels. The major, or primary, level decides the base letter, the next level decides diacritics, and the next decides case. That layered weighting is what lets “cafe”, “café” and “CAFE” interleave the way a Spanish reader expects, instead of the way their byte values happen to arrange them.
A linguistic index is simply the index that makes a linguistic collation fast. It is a functional index built on the NLSSORT expression:
CREATE INDEX nls_index ON my_table (NLSSORT(name, 'NLS_SORT = SPANISH'));
NLSSORT returns the pre-computed collation key for a value, and the index stores those keys. The documentation is explicit about the trade: a linguistic index slows down inserts and updates, but it removes the cost of a full in-memory sort on every ORDER BY or range WHERE that relies on that collation. Without it, the database has to build and sort the collation keys on the fly for every query; with it, the keys are already on disk and in order.
Here is the limitation that matters for our story. A linguistic collation is designed to reproduce the ordering of a language. You point it at SPANISH, GERMAN, or GENERIC_M, and it orders like that language does. You cannot point it at “IBM code page 37” and have it reproduce the byte ordering of that code page, because a code page’s byte order is not a language – it is an arbitrary encoding, and no linguistic collation models “the way the bytes of EBCDIC 37 line up.” On 19c, that left the EBCDIC-ordering problem with no clean answer: accept standard binary order and risk the silently-wrong predicates, hand-build a custom collation to approximate it, or live with the approximation. That is precisely the gap 26ai’s emulated binary collation fills.
What Oracle 26ai actually shipped
The release is best understood as four concrete pieces working together, rather than a single magic switch.
First, Oracle added a family of IBM CDRA-compatible EBCDIC client character sets. CDRA is IBM’s Character Data Representation Architecture, the formal specification of how each EBCDIC code page maps to other encodings. These new character sets implement those mappings accurately, so character conversion during migration – and during subsequent client-to-server traffic – is predictable and consistent with IBM’s published standards. I will unpack what “client character set” means in practice in the next section, because it is the part that surprises people.
Second, Oracle introduced built-in emulated EBCDIC binary collations, named things like BIN_IBM37, one generated for each supported IBM EBCDIC code page. Each one reproduces the exact byte-level ordering of its source code page inside the database. This is the piece that solves the invisible ordering problem directly.
Third, Oracle leans on Data-Bound Collation – a feature that has existed since 12.2 but was waiting for a reason like this one – as the deployment vehicle. Instead of setting the collation per session, Data-Bound Collation lets you pin a collation to a column, a table, or a schema declaration. That turns VARCHAR2(100) COLLATE BIN_IBM37 into a permanent property of the schema rather than a session setting someone has to remember to set on every connection.
Fourth, Oracle shipped a dedicated migration chapter – chapter 12, “EBCDIC Emulation for Mainframe Migration” – in the Globalization Support Guide, covering how to pick source and target character sets and how to verify round-trip conversion.
Because the EBCDIC collations now ship inside the database itself, they work identically on Exadata, on Autonomous Database, on Exadata Cloud@Customer, and across multi-cloud deployments.
CDRA-accurate character sets: fixing the silent mapping bugs
The character-set side of the story is the subtler half, and it is worth understanding because the old behaviour was not broken in a loud way – it was broken in a way that produced subtly wrong data.
Oracle has supported EBCDIC character sets before, most notably WE8EBCDIC37. The problem is that some of its mappings did not match IBM’s published standards. The classic example is the newline pair: the old implementation mapped both control code 0x15 (New Line) and 0x25 (Line Feed) to Unicode U+000A, collapsing two distinct EBCDIC characters into a single output. The CDRA-compatible sets fix this, mapping 0x15 to U+0085 (the Unicode Next Line character) and preserving the distinction IBM’s specification actually defines.
Why does that matter? Because in migration, a collapsed mapping is not an error you catch in testing – it is a silent data change that only surfaces later, often when two systems disagree about what a record should contain. The new character sets give you “source-to-target character mappings that are compatible with IBM’s published standards,” which is the technical way of saying: what comes out of the mainframe is what goes into Oracle, character for character.
One operational point is worth being explicit about, because it answers the question “wait, is the database going to run in EBCDIC?” No. These CDRA-compatible sets are client-only character sets. They are not the character set your Oracle database runs on, and you do not set them as the database’s own NLS_LANG. They are used only in the places where data actually crosses the boundary into the database – the loading and conversion paths. The database itself should run on a standard modern character set, and the recommended destination is AL32UTF8 (UTF-8).
The reason AL32UTF8 is the only destination with a hard claim behind it is simple: it is the only Oracle character set capable of representing every character from any EBCDIC code page. That is exactly what you need when you are consolidating multiple source code pages and multiple language groups onto a single target. The legacy single-byte fallbacks – WE8ISO8859P1, WE8ISO8859P15, WE8MSWIN1252 – each carry a trade-off (the first lacks the euro sign, the second omits some punctuation, the third drops control codes), so they exist for narrow compatibility cases rather than as the default.
Concretely, the migration chapter walks through the main loading paths, and in each one the EBCDIC source code page is named at the boundary while the target stays AL32UTF8:
- SQL*Loader, where you specify
CHARACTERSET IBM1140(orIBM273, or whichever code page you are coming from) directly in the control file, and the loader performs the conversion into the database character set automatically. - External tables using
ORACLE_LOADER, where theACCESS PARAMETERSclause carries theCHARACTERSETdirective. - DBMS_LOB, through
LOADCLOBFROMFILEandCONVERTTOCLOB, taking the EBCDIC code page viaNLS_CHARSET_ID('IBM1140'). - UTL_I18N and UTL_RAW, for custom conversion of
RAWtoVARCHAR2and back when your pipeline does not fit the standard loaders.
Emulated binary collations: EBCDIC ordering without the mainframe
The second half of the feature is where the real migration risk lives, because this is the half that stays invisible until it bites you.
Recall the ordering problem from the opening section. EBCDIC and ASCII assign different byte values to the same characters, so the same set of values lines up in a different order under each. A concrete example: in EBCDIC, letters sort before digits. Consider a product code column that was always queried on a mainframe with a predicate like WHERE product_code >= '0'. On the mainframe, that returns only the numeric codes – correct. Move that same data to a database using standard binary ordering and run the identical predicate, and you now get numeric and alphanumeric codes, because in ASCII ordering letters come after digits. The predicate still runs. It returns rows. It is just wrong. The dashboard stays green, and nobody notices until a reconciliation fails months later.
The emulated binary collations solve exactly this. A collation like BIN_IBM37 is generated specifically for the IBM code page 37, and it reproduces the source’s byte-level ordering precisely. Mechanically, the documentation describes the approach clearly: an emulated binary collation converts the value into the collation’s base character set, then applies binary comparison against that converted value. Characters in the database character set that are not defined in the base character set are treated as equal to the question mark (?), the default replacement character. For a handful of functions such as INSTR, the conversion step is skipped and standard binary comparison is applied directly.
Two properties make these collations genuinely practical, not just theoretically correct. The first is size, and the reason for it is worth spelling out. A binary index stores the column’s own bytes as its key – a one-to-one copy, so the index key is exactly as wide as the column value it came from. A linguistic index does something different: it stores a collation key, the numeric sort representation that the collation builds from the value (the major-plus-minor pair for a single-language sort, or the primary, secondary and tertiary levels for a multilingual or UCA sort). Because that representation is an expansion of the original, the documentation sizes the worst-case collation key for a column of maximum byte length n at roughly n*8 + 10 bytes for a typical linguistic collation, and n*21 + 5 for UCA-based collations – in other words, many times the width of the column it indexes. That is the concrete reason a linguistic index on a character column is a lot bigger than the binary index on the same column, and why it carries extra storage and CPU. The emulated binary collations sidestep this entirely: instead of building an expanded linguistic collation key, they order by comparing the converted value with plain binary ordering, so the index they drive stays close in size to a standard binary index and does not carry the n*8 + 10 expansion that a linguistic index on the same column would. You get the EBCDIC ordering behaviour without the space penalty a hand-built linguistic collation would impose. The second property is precision: they are generated per code page, so BIN_IBM37 orders like code page 37, not like some generic “EBCDIC-ish” approximation.
There is one operational catch worth flagging for the DBA planning the migration. Because these are linguistic collations, they require linguistic indexes – a functional index on NLSSORT(column, 'NLS_SORT=BIN_IBM37') – to serve the predicates efficiently. A standard binary index cannot satisfy a query that relies on an emulated binary collation. That is a planning detail, not a blocker, but it is the kind of thing you want in the migration runbook from day one rather than discovering it during a performance review.
The worked example in the documentation is the clearest illustration of what changes. With a column declared COLLATE BIN_IBM37, a query like WHERE ename > '0' returns no rows selected – which is the correct EBCDIC behaviour – whereas the same query under standard binary ordering returns a handful of alphabetic names. One declaration, and a predicate that would have been silently wrong after migration is silently right. That is the value of this feature in a single sentence.
Data-Bound Collation: the deployment vehicle
A collation is only as useful as your ability to apply it reliably, and this is where the design gets genuinely clean.
You can apply an emulated binary collation the old-fashioned way, at the session level, with NLS_COMP=LINGUISTIC and NLS_SORT=BIN_IBM37. That works, but it is blunt: it applies to all character processing in the session, it is heavier than plain binary, and – the killer in practice – it depends on someone remembering to set it, on every connection, forever. Session state is exactly the kind of thing that survives the migration project and is forgotten two years later when a new connection pool is configured by someone who was not in the room.
Data-Bound Collation removes that fragility. Since 12.2, Oracle has let you declare the collation as part of the schema itself: VARCHAR2(100) COLLATE BIN_IBM37 on the column, or at table or schema level. The collation becomes a property of the data, not of the session. The predicate is correct wherever it is executed, because the collation travels with the column definition.
The combination is what makes the EBCDIC story deployable on modern platforms. You get granular, column-level EBCDIC ordering, you avoid the session-state trap, and – critically – you avoid customized locale data entirely, which is what allows the whole thing to run on Exadata and Autonomous Database where custom locale data is unsupported. Declarative, portable, and self-documenting: that is the kind of design that ages well.
Wrapping up
This article is about a specific technical capability, but the reason it matters is strategic, so let me close with the version I would give a CIO or CTO.
The mainframe is not going away. It still runs the core of European banking, insurance, and government, and the industry’s own surveys show institutions intend to keep investing in it for years. At the same time, the cost of that dependency is enormous, and Europe has added a new reason to act: DORA, in force since the start of 2025, now treats an over-reliance on a single mainframe platform as a risk that institutions must actively manage and reduce over time. That combination – persistent mainframes, real cost pressure, and new regulatory obligations – is why modernization has moved from “someday” to “we need a plan.”
The obstacle has never been moving the data. It has been moving the data without subtly changing its meaning. The mainframe’s character encoding orders information differently from every modern system, and that difference has historically broken migrations in ways that are invisible until they are expensive. What has changed is that Oracle has now built a solution for this into its database: accurate conversion that matches the mainframe’s own published standards, and ordering behaviour that preserves the mainframe’s logic without rewriting the queries that depend on it.
What this means in practice is that an organization can now move a mainframe workload onto modern Oracle infrastructure and keep its existing queries returning the same correct results – no silent ordering changes, no green dashboards hiding wrong records. It removes a technical blocker that has quietly stalled these projects for years, and it does so in a way that is built into the database itself, so it works wherever the database runs.
The bottom line is this: if your organization is under pressure to reduce mainframe dependency – for cost, for resilience, or because a regulator now expects you to – one of the oldest excuses for not starting has just been rendered obsolete. The bridge is built. The question is no longer whether you can preserve your mainframe logic off the mainframe; it is when you will choose to cross it.

