All Tools View Categories Blog About Contact Privacy

Why Does My Text Show Weird Symbols? Encoding Errors (Mojibake) Explained

Why Does My Text Show Weird Symbols? Encoding Errors (Mojibake) Explained

Why does my text show weird symbols? Why does your text show Café, ’, or �? That’s mojibake — UTF-8 bytes misread as Windows-1252 or vice versa. Your file was saved as UTF-8 (Café = bytes C3 A9), but the browser, database, or editor decoded it as Windows-1252 (C3 → Ã, A9 → ©) — so one correct byte pair becomes two wrong characters. Since 2023, 98% of the web is UTF-8, yet one missing <meta charset="utf-8">, a mis-labeled CSV, or a double-encode can still garble every accent, emoji, and quote. This guide explains what encoding actually is, the 7 patterns that reveal the exact mismatch, and how to fix mojibake in code, editors, and databases without guessing.

TL;DR — Why Text Shows Weird Symbols (Mojibake):
  • One line: Text is bytes + a label (charset). If writer saves as UTF-8 but reader decodes as Windows-1252 (or vice versa), bytes are mapped to wrong characters — that’s mojibake. See W3C What Is Encoding + MDN Character Encoding.
  • Spot the pattern: é → é (UTF-8 read as 1252), “ → “ (smart quotes double-mojibaked), (replacement char FFFD for invalid bytes), ???? (bytes stripped). Pattern tells the exact mismatch.
  • Why UTF-8 wins: ASCII 0–127 = 1 byte (compatible), 128+ = 2–4 bytes (é = C3 A9, 😀 = F0 9F 98 80) per RFC 3629 UTF-8 + Unicode 15. Windows-1252 is single-byte (é = E9) — mixing them garbles.
  • Fix loop (30 sec): 1) Declare UTF-8 everywhere: HTML <meta charset="utf-8"> first in <head>, HTTP Content-Type: text/html; charset=utf-8, DB utf8mb4_unicode_ci, file saved as UTF-8 without BOM. 2) Convert: iconv -f windows-1252 -t utf-8 file.txt or JS new TextDecoder('utf-8').decode(bytes) (MDN TextDecoder). 3) Re-validate: no �, no é.
  • Tooling: Detect via chardet / “what the heck is this” test, fix via our text tools (encoding converter) or editor (VS Code → Reopen with Encoding). See Joel Spolsky’s must-read Absolute Minimum on Unicode.
what is mojibake encoding error weird symbols cafe mojibake example

What Is Mojibake? The 5-Second Example

Mojibake is correct bytes decoded with the wrong charset — the classic Café → Café.

Try it live: save Café as UTF-8 → bytes are 43 61 66 C3 A9 (C3 A9 is é in UTF-8). Open that file as Windows-1252 → C3 maps to Ã, A9 maps to © → you see Café. Reverse: save Café as Windows-1252 → byte E9 for é → read as UTF-8 → E9 alone is invalid UTF-8 → browser shows Caf� (U+FFFD replacement). The bytes never changed — only the label did. That’s why it’s not “corruption” but mislabeling.

I see this weekly when a CSV exported from Excel (Windows-1252 by default on old Windows) is imported into a MySQL table set to utf8mb4 without conversion — every “é” becomes “é” in the web app, while emojis become “????” because Windows-1252 has no mapping for 4-byte emoji. The fix wasn’t to “clean the data” but to convert at import: iconv -f cp1252 -t utf8 data.csv.

Charset vs Encoding vs Unicode — 3 Terms in One Line

  • Character set / repertoire: The list of characters (A, é, 😀) — Unicode is the universal list (149k chars in Unicode 15 per Unicode 15).
  • Encoding / charset: How each character is mapped to bytes — UTF-8 (variable 1–4 bytes, ASCII compatible), Windows-1252 (single byte, 256 slots), UTF-16. See W3C Definitions and MDN.
  • Mojibake: Decoding bytes with a different encoding than they were written — the mismatch, not the bytes themselves.

How Encoding Actually Works — Bytes, Not Magic

Text on disk is always bytes; the charset tells the decoder how to turn bytes back into characters. Per RFC 3629 and W3C:

utf-8 vs windows-1252 encoding bytes comparison ascii unicode
CharacterUTF-8 Bytes (Hex)Windows-1252 BytesWhat You See If Mismatched
A (U+0041)41 (1 byte)41Same — ASCII is identical (why English rarely mojibakes)
é (U+00E9)C3 A9 (2 bytes)E9 (1 byte)UTF-8→1252: Café | 1252→UTF-8: Caf�
“ (U+201C)E2 80 9C (3 bytes)— (no 1-byte slot)UTF-8→1252: “ (3 chars)
😀 (U+1F600)F0 9F 98 80 (4 bytes)— (needs surrogate)UTF-8→1252: 😀 or ????

Why UTF-8 won: it’s ASCII-compatible (English text is identical bytes in both), self-synchronizing, and covers all Unicode. W3C recommends utf-8 for all new content (W3C). Joel Spolsky’s classic “There ain’t no such thing as plain text” (Joel on Software) makes the same point: a string is bytes plus an encoding label — forget the label and you get mojibake.

Where the Label Lives — 3 Places That Must Agree

  1. File bytes + BOM: The file itself may start with EF BB BF (UTF-8 BOM) — discouraged for web, but some editors add it. BOM can break JSON/CSV parsers if unexpected.
  2. HTTP header: Content-Type: text/html; charset=utf-8 — highest priority for browsers.
  3. Meta tag: <meta charset="utf-8"> as first child of <head> — fallback if header missing.

If these disagree, browser follows HTTP header > BOM > meta (per HTML spec). That’s why your local .html looks fine (meta says utf-8) but the same file on the server mojibakes (server sends charset=windows-1252). Check DevTools → Network → Response Headers → content-type.

Why Does My Text Show Weird Symbols? The 7 Mojibake Patterns

Each garble is a fingerprint — learn to read it and you know the exact fix without guessing.

7 mojibake patterns weird symbols table utf8 windows1252 double encoding
#You SeeActualMismatchFix
1CaféCaféUTF-8 bytes read as 1252Decode as 1252 → re-encode as UTF-8
2Caf� or Caf?Café1252 bytes read as UTF-8 (invalid → FFFD)Decode as UTF-8 → read as 1252 correctly
3“Hello”“Hello”UTF-8 smart quotes (3 bytes) read as 1252Same as #1 — convert
4😀 or ????😀4-byte emoji via 1252 (no glyph) or strippedUse UTF-8 end-to-end, utf8mb4 in DB
5CaféCaféDouble-encoded (UTF-8 → 1252 → UTF-8 again)Encode once, decode once
6HelloHelloUTF-8 BOM EF BB BF rendered as textSave as UTF-8 without BOM
7文字 (Chinese garbled as Latin)文字UTF-8 Chinese (3 bytes each) read as 1252Declare UTF-8 in HTTP + meta + DB

Pattern 1: é — The Classic (UTF-8 → 1252)

One UTF-8 character becomes two 1252 characters. é = C3 A9 in UTF-8 → 1252 maps C3→Ã, A9→© → é. Similarly, (E2 80 94) → —. Count: every non-ASCII becomes 2–3 garbled chars — that doubling is diagnostic. Fix in code: treat the mis-decoded string’s bytes as 1252 then decode as UTF-8. In Python: "Café".encode("cp1252").decode("utf-8") → "Café" (works because é round-trips). In JS: it’s already garbled after fetch — fix the source charset header, not the string after.

Pattern 2: � — The Replacement Character (1252 → UTF-8)

(U+FFFD) means the decoder hit an invalid UTF-8 byte sequence and substituted. Windows-1252 é is single byte E9 — that byte alone is invalid UTF-8 (needs leading C2–F4) → decoder replaces with . You lose information: E9 cannot be reversed to é — you must re-fetch with the right encoding. Check DevTools → Network → Response → preview vs raw bytes. Browser shows when header says charset=utf-8 but body is actually Windows-1252.

Pattern 5: é — Double Mojibake (The Double Fix Trap)

Double-encode happens when you “fix” by re-encoding already-garbled text as UTF-8 again. Original UTF-8 C3 A9 → misread as 1252 → é (bytes C3 A9 interpreted) → saved again as UTF-8 → each of à (C3 83) and © (C2 A9) becomes 2 bytes → total 4 bytes C3 83 C2 A9 → read as 1252 → é. Fixing double requires two reversals: encode 1252 → decode UTF-8 twice, but better to fix source once — every extra encode worsens loss with .

How to Diagnose in 10 Seconds — Look at the Garble

Don’t guess — the characters tell you the mismatch.

how to diagnose mojibake fix encoding error flowchart
  1. See Ã, Â, â? → UTF-8 read as Windows-1252/Latin-1. Fix: decode bytes as 1252 then re-decode as UTF-8, or re-save source as UTF-8 and serve charset=utf-8.
  2. See � or ? → Invalid UTF-8 (often 1252 bytes read as UTF-8, or data stripped to ? by a 7-bit path). Check HTTP header vs meta vs DB charset — one disagrees.
  3. See  at start? → BOM EF BB BF — save file as UTF-8 without BOM (VS Code → Save with Encoding).
  4. See ???? for emoji? → DB column is utf8 (3-byte) not utf8mb4 (4-byte) — MySQL utf8 truncates 4-byte emoji to ?. ALTER to utf8mb4.
  5. Check headers: DevTools → Network → click document → Response Headers → content-type. If it says charset=windows-1252 but your <meta charset="utf-8"> says utf-8, header wins — fix server config.
# Quick proof — what bytes are actually on disk?
# Linux/macOS
hexdump -C file.txt | head
# Café as UTF-8 → 43 61 66 c3 a9
# Café as Windows-1252 → 43 61 66 e9

# Python — detect and fix classic é pattern
s = "Café"  # mojibaked
fixed = s.encode("cp1252").decode("utf-8")
print(fixed)  # Café

# Node — fetch with wrong charset (simulate)
# Headers: content-type: text/html; charset=windows-1252  but body is utf-8 bytes
# Fix: ensure server sends charset=utf-8, or use TextDecoder
const bytes = new Uint8Array([0x43, 0x61, 0x66, 0xC3, 0xA9]);
new TextDecoder("utf-8").decode(bytes)  // Café
new TextDecoder("windows-1252").decode(bytes)  // Café

How to Fix It — By Where You Control It

Fix at the source (where bytes are written), not after they’re garbled — re-encoding � loses data.

Fix 1: HTML / HTTP — Declare UTF-8 Once, Everywhere

<head>
  <meta charset="utf-8">  <!-- first element in head -->
</head>

And HTTP header (highest priority):

# Apache .htaccess
AddDefaultCharset UTF-8

# Nginx
charset utf-8;

# Node/Express
res.setHeader("Content-Type", "text/html; charset=utf-8");

Verify: curl -I https://example.com | grep -i content-type must show charset=utf-8. See W3C for why meta must be first.

Fix 2: Files — Save as UTF-8 Without BOM

VS Code: Save with Encoding → UTF-8 (not “with BOM”). Notepad++: Encoding → Convert to UTF-8 without BOM. Excel “Save as CSV UTF-8” is often actually cp1252 with BOM — verify with hexdump before importing. Our text tools converter shows byte preview so you see C3 A9 vs E9 before saving.

Fix 3: Databases — Use utf8mb4, Not utf8

MySQL’s utf8 is not UTF-8 — it’s 3-byte max, so emoji (4-byte) truncates to ? or ????. Use utf8mb4:

ALTER DATABASE mydb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE posts CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- For existing mojibaked rows: fix by converting via binary
UPDATE posts SET title = CONVERT(CAST(CONVERT(title USING latin1) AS BINARY) USING utf8mb4)
WHERE title LIKE '%Ã%';

Postgres is UTF-8 by default — ensure client encoding matches (SET client_encoding TO 'UTF8'). See Joel on Software for why “plain text” has no meaning without encoding.

Fix 4: CSV / Excel — Convert at Import, Not After

Excel on Windows defaults to cp1252. When importing to a UTF-8 system, convert on the fly: iconv -f cp1252 -t utf-8 data.csv > data.utf8.csv or python -c "import codecs; open('out.csv','w',encoding='utf-8').write(open('data.csv',encoding='cp1252').read())". In JS, use TextDecoder with the source encoding label if you must read legacy bytes. Don’t “fix” by replacing éé manually — you’ll miss “ and double-encode others.

Fix 5: Double Mojibake — Stop Re-Encoding Garbled Text

If you already saved mojibaked text to DB, don’t run a blanket replace — it double-encodes the é that was once é. Instead, restore from pre-mojibake backup or reverse once: SELECT CONVERT(CAST(CONVERT(col USING latin1) AS BINARY) USING utf8mb4) for the single-mismatch case. If é (double), apply twice but test on a copy first — �-replaced rows are irreversible.

prevent encoding errors checklist utf-8 meta header database

Why Copy-Paste From Word, Excel, and PDFs Breaks Text

Word, Excel, and PDFs don’t use plain ASCII quotes — they use smart quotes, em dashes, and non-breaking spaces that are 2–3 byte UTF-8 sequences invisible until they mojibake.

Word’s “Hello” is not "Hello" — it’s E2 80 9C 48 65 6C 6C 6F E2 80 9D (left/right smart quotes are 3 bytes each). So is Excel’s non-breaking space C2 A0 (looks like a space, but it’s not 20) and PDF’s ligature �. When you copy that into a system that expects Windows-1252 or strips to ASCII, each smart quote becomes “ → 3 garbled chars per quote. I see this after every “Paste from Word” into a CMS that saves as Latin-1 — the article looks fine in Word, then shows — for every em dash on the live site.

Fix: paste via “Paste as plain text” (Ctrl+Shift+V) or run through a plain-text step: Word → Notepad (which forces UTF-8) → CMS, or use our text tools “Smart quotes → straight quotes” converter that maps E2 80 9C22 and C2 A020 before saving. In code, normalize on input: Python text = unicodedata.normalize('NFKC', text).replace('\xa0',' ') — that NFKC folds ligatures and compatibility chars back to ASCII where appropriate.

PDF copy is worse: PDF text extraction often inserts for unmapped glyphs and line-break hyphens. If you extract with pdftotext -enc UTF-8 without that flag, you get Windows-1252 bytes labeled as UTF-8 → mojibake on every accented word. Always specify encoding at extract time and verify with hexdump, not by eye.

Best Practices Checklist — Keep Text UTF-8 End-to-End

LayerSet ToDon’t
FileSave as UTF-8 without BOM (VS Code)UTF-8 with BOM for JSON/CSV (breaks parsers)
HTML<meta charset="utf-8"> first in headNo meta or meta after 1024 bytes
HTTPContent-Type: ...; charset=utf-8Server default charset=iso-8859-1
DBMySQL utf8mb4_unicode_ci / Postgres UTF8MySQL utf8 (3-byte) for emoji
CodeRead with explicit encoding: decode('utf-8')Rely on locale default (open().read() without encoding)
CSVExport/Import as UTF-8, verify with hexExcel cp1252 → utf8mb4 without convert

Automate: add file --mime-encoding *.csv to CI, and in JS always create text via TextEncoder('utf-8') rather than concatenating bytes manually. For quick fixes, paste garbled text into our text tools encoding converter — it shows byte preview (C3 A9 vs E9) so you see the mismatch before saving.

Practice Lab — Fix Mojibake in 2 Minutes

# Lab — no install
1) Create a file with Café as UTF-8: echo -n "Café" | hexdump -C  # C3 A9
2) Misread as 1252: python -c "print(open('file.txt',encoding='utf-8').read().encode('utf-8').decode('cp1252'))"
   # → Café (classic pattern)
3) Fix in Python: python -c "print('Café'.encode('cp1252').decode('utf-8'))"  # → Café
4) Check your page: DevTools → Network → Headers → content-type charset → must be utf-8
5) Fix at source: HTML <meta charset="utf-8"> first in head + HTTP header + DB utf8mb4
# You just proved: same bytes (C3 A9) → decoded two ways → mojibake vs correct

You just reproduced the exact byte-level cause that makes every accent double — and the one-line fix that reverses it for the classic pattern.

Frequently Asked Questions

Why does my text show é instead of é?

That’s UTF-8 bytes C3 A9 (é) read as Windows-1252, where C3→à and A9→©. Your file was saved as UTF-8 but the browser/server decoded it as 1252. Fix: ensure the source sends charset=utf-8 (meta + HTTP header) and re-save the file as UTF-8. In Python, "Café".encode("cp1252").decode("utf-8") reverses the classic case. See W3C.

Why do I see � (black diamond question mark)?

is U+FFFD, the Unicode replacement character — the decoder hit an invalid byte sequence for the declared charset (often Windows-1252 bytes read as UTF-8, where single byte E9 is invalid). Unlike é, this one loses data: E9 can’t be reversed. You must re-fetch with the correct encoding label instead of re-encoding the � string. See MDN TextDecoder.

What’s the difference between UTF-8 and Windows-1252?

UTF-8 is variable-length (1–4 bytes, ASCII compatible) and covers all Unicode via RFC 3629; Windows-1252 is single-byte (256 characters, Western Europe) where é is one byte E9. UTF-8 é is two bytes C3 A9 — mixing them causes mojibake. Use UTF-8 everywhere new; reserve 1252 only for legacy files you convert. See MDN.

How do I fix mojibake in MySQL where emoji shows as ????

MySQL’s utf8 is 3-byte and truncates 4-byte emoji (F0 9F 98 80) to ?. Convert to utf8mb4: ALTER DATABASE db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ALTER TABLE t CONVERT TO CHARACTER SET utf8mb4;. For existing mojibaked rows with é, run CONVERT(CAST(CONVERT(col USING latin1) AS BINARY) USING utf8mb4) once — test on a copy because � rows are irreversible.

Can I just replace é with é to fix it?

Don’t — that fixes one character but misses “, —, emoji, and double-encoded é. Blind replace also double-encodes already-fixed text. Use a proper converter: bytes C3 A9 (UTF-8) vs E9 (1252) — tools that show hex preview let you verify. Our text tools converter previews bytes so you confirm before saving.

Why does clearing cache not fix mojibake?

Because mojibake is an encoding label mismatch, not a cached file — the bytes are correct but decoded wrong. Clearing cache re-downloads the same bytes with the same wrong label, so garble returns. Fix the label: HTTP charset=utf-8 or <meta charset="utf-8">, not the cache.

How do I know what encoding my file actually is?

Use file --mime-encoding file.txt (Linux/macOS) or hexdump: Café as UTF-8 is 43 61 66 C3 A9, as Windows-1252 is 43 61 66 E9. In VS Code, status bar shows encoding — click to “Reopen with Encoding” to test. Browser DevTools → Network → Headers shows what charset the server claimed.

Should I save files as UTF-8 with BOM?

No for web/JSON/CSV — BOM EF BB BF renders as  at the start (pattern #6) and breaks JSON parsers. Save as UTF-8 without BOM (VS Code: Save with Encoding → UTF-8). Use BOM only if a legacy Windows tool requires it.

Last updated: September 2, 2026 • Author: Toolwasp Team • Sources verified Sep 2, 2026: W3C What Is Encoding, MDN Character Encoding, MDN TextDecoder, RFC 3629 UTF-8, Unicode 15, Joel on Software Unicode, W3C Definitions, Toolwasp Text Tools.