Skip to content
ASCII World

Mojibake: Why Text Displays as Garbage Characters

How Character Encoding Mismatches Corrupt Digital Text

By the ASCII World team

The Mechanics of Encoding Mismatches

The byte sequence 0xC3 0xA9 represents the lowercase accented letter é (Unicode code point U+00E9) in UTF-8. When an application configured for Windows-1252 reads those two bytes, it treats each byte as an independent single-byte character: 0xC3 maps to à and 0xA9 maps to ©. The user sees é instead of the intended letter.

Computers store text strictly as numeric byte values. A file or network payload containing the byte 0xE4 has no intrinsic visual shape. In ISO 8859-1 (Latin-1), 0xE4 represents the German umlaut ä. In KOI8-R (a standard Cyrillic encoding), that same 0xE4 represents the Cyrillic letter д. In Shift JIS, 0xE4 cannot stand alone; it is a lead byte that requires a second byte to form a Japanese Kanji character. If software reads 0xE4 without being told the correct scheme, it must guess or apply a default setting, producing mojibake whenever the assumption fails.

Standard 7-bit ASCII characters (byte values 0x00 through 0x7F) remain unaffected by this specific mismatch because modern encodings, including UTF-8, ISO-8859 variants, and Windows code pages, maintain backward compatibility with the basic ASCII table. Mojibake almost exclusively targets characters outside this 7-bit range: accented letters, non-Latin alphabets, typographic punctuation, symbols, and emoji.

Common Mojibake Signatures and What Causes Them

Because specific encoding mismatches happen repeatedly across software stacks, certain strings of garbled characters form recognizable signatures. Identifying the pattern reveals the source encoding and the erroneous target encoding.

Intended Text Expected Code Point Displayed Mojibake Cause
é U+00E9 é UTF-8 bytes (0xC3 0xA9) read as Windows-1252 or ISO-8859-1
’ (smart apostrophe) U+2019 ’ UTF-8 bytes (0xE2 0x80 0x99) read as Windows-1252
“ (left double quote) U+201C “ UTF-8 bytes (0xE2 0x80 0x9C) read as Windows-1252
- (em dash) U+2014 — UTF-8 bytes (0xE2 0x80 0x94) read as Windows-1252
日本語 (Japanese) U+65E5 U+672C U+8A9E æ-¥æœ¬èªž UTF-8 bytes (9 bytes) read as ISO-8859-1
文字化け U+6587 U+5B57 U+5316 U+3051 æ-‡å-åŒ-ã ‘ UTF-8 bytes read as Windows-1252 / Latin-1
テスト (Test) U+30C6 U+30B9 U+30C8 ÆËÈ (approx) Shift JIS read as ISO-8859-1

The appearance of à followed by another symbol is the most frequent signature on the web. In how UTF-8 works, two-byte sequences representing characters in the U+0080 to U+07FF range (which covers Latin extensions, Greek, Hebrew, and Arabic) start with a byte between 0xC2 and 0xDF. In Windows-1252 and ISO-8859-1, 0xC3 is the character Ã, making it appear constantly whenever accented text gets misread.

Three-byte UTF-8 sequences (such as typographic quotes, mathematical symbols, and CJK characters) start with bytes from 0xE0 to 0xEF. In Windows-1252, 0xE2 renders as â, leading to the ubiquitous ’ pattern in poorly migrated content management systems.

The Multi-Layer Pipeline: Where Metadata Gets Dropped

Mojibake does not happen randomly inside a CPU; it happens at boundaries between systems where character set metadata is omitted, contradictory, or overridden. A typical web application moves data through at least four distinct layers, each with its own encoding configuration.

In the database layer, a table might store columns encoded in UTF-8, but the database client connection may default to latin1. When the database sends the UTF-8 bytes over the connection, it attempts to translate what it assumes is latin1 data, corrupting the stream before it even reaches the backend code.

In the HTTP transmission layer, the server might send a header such as Content-Type: text/html; charset=ISO-8859-1, while the HTML file itself contains <meta charset="utf-8">. Per the W3C and IETF specifications, HTTP headers take precedence over inline document meta tags. Browsers following standard protocol rules will obey the HTTP header and parse the page using ISO-8859-1, converting all multibyte UTF-8 characters into garbled text.

File input and output is another common failure point. When a developer opens a text file using an API without explicitly declaring an encoding (such as calling Python's open('data.txt') in Python 3 on Windows, which defaults to the system code page like CP1252 rather than UTF-8), the runtime reads the bytes through the local operating system default. Moving that exact script to a Linux server where the locale defaults to UTF-8 changes the program's runtime behavior without any code modifications.

Reversible Mojibake vs. Irreversible Data Loss

Not all corrupted text can be recovered. The ability to reverse mojibake depends on whether the misinterpretation process formed a bijective (one-to-one) mapping of raw byte values, or whether it discarded unmapped bytes.

If a UTF-8 string was misread as Windows-1252 and saved directly back to a database or file as characters, the recovery is often possible because Windows-1252 maps almost all byte values from 0x00 to 0xFF to distinct characters. You can take the displayed string, extract the Windows-1252 byte representation for each character, and then re-decode those exact bytes as UTF-8 using a dedicated mojibake fixing process.

Data becomes permanently unrecoverable in three scenarios:

  • The Replacement Character (U+FFFD): When a strict UTF-8 parser encounters byte sequences that violate UTF-8 structural rules, it replaces the invalid bytes with the Unicode Replacement Character (displayed visually as a black diamond with a white question mark: ). Once a sequence like 0x80 0x81 is overwritten with 0xEF 0xBF 0xBD (the UTF-8 encoding of U+FFFD), the original byte data is gone.
  • Unassigned Code Points or Question Mark Fallbacks: Older APIs and legacy conversions often substitute ASCII question marks (0x3F / ?) or control codes whenever a byte does not exist in the target character set. In ISO-8859-1, byte values 0x80 through 0x9F are unassigned control characters; parsers encountering them often drop them or replace them with ?.
  • Lossy Encoding Conversions: If text originally in Japanese Shift JIS is converted to standard ASCII using a lossy fallback mechanism, characters without ASCII equivalents turn into literal question marks (???). No mathematical operation can distinguish which Japanese kanji produced which question mark.

Double Encoding: When Mojibake Compounds

A particularly destructive error is double encoding. This happens when a program takes a string that is already UTF-8, mistakenly interprets the individual bytes as characters in Windows-1252 or Latin-1, and then encodes that new, incorrect string back into UTF-8 a second time.

Consider the character é:

  1. The original character is é (Unicode code point U+00E9).
  2. Encoded correctly to UTF-8, it yields 2 bytes: 0xC3 0xA9.
  3. A misconfigured system reads these bytes as Windows-1252, producing two characters: Ã (U+00C3) and © (U+00A9).
  4. The system saves these two characters by encoding them to UTF-8. Ã becomes 0xC3 0x83, and © becomes 0xC2 0xA9.
  5. The resulting byte sequence is now 4 bytes: 0xC3 0x83 0xC2 0xA9.

When this double-encoded sequence is viewed in a UTF-8 application, it renders as é. If it is subsequently misread as Windows-1252 a second time, it expands into four characters: é. Each iteration doubles the size of multibyte characters and introduces deeper layers of structural corruption, making automated automated repair significantly more complex.

Preventing Mojibake in Modern Systems

The standard industry defense against mojibake is strict end-to-end UTF-8 normalization across all layers of the stack. Eliminating encoding ambiguity requires explicit configuration at every interface:

  • Declare HTTP Headers Explicitly: Ensure web servers and application frameworks transmit Content-Type: text/html; charset=UTF-8 or Content-Type: application/json; charset=UTF-8 in HTTP response headers rather than relying on browser sniffing or document-level declarations.
  • Configure Database Collations and Connections: Set database character sets to utf8mb4 (in MySQL/MariaDB) or native UTF-8 (in PostgreSQL), and ensure the database connection string explicitly sets client encoding to UTF-8 upon connection.
  • Specify File I/O Encodings: Never rely on runtime platform defaults when reading or writing files in languages like Python, Java, or Node.js. Always supply the encoding parameter (e.g., encoding='utf-8') directly in file access calls.
  • Normalize API Payloads: Enforce UTF-8 parsing on all incoming REST, GraphQL, or webhook inputs, rejecting or sanitizing payloads that deliver malformed byte sequences before writing them to long-term storage.

References

  1. Unicode Standard Core Specification - Character Encoding Concepts
  2. RFC 3629: UTF-8, a transformation format of ISO 10646
  3. W3C Encoding Standard
  4. RFC 7231: Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content