Back to Character Encoding

utf8mb4 UTF-8 vs. MySQL's utf8 and utf8mb4 Unicode Flagship

MySQL's 'utf8' charset is not real UTF-8 — it silently truncates emoji and rare characters. Here's the gotcha, and the fix.

What it is

This isn't a separate encoding — it's a MySQL-specific footgun. MySQL's utf8 character set, introduced back when 3-byte encoding covered essentially all practically-used Unicode characters, was never updated to support the full 4-byte range once Unicode expanded (emoji, many historic scripts, some rarer CJK characters, mathematical symbols). Rather than fix utf8 and risk breaking every existing table, MySQL added a new charset — utf8mb4 — that implements complete, correct UTF-8. The old utf8 was left in place, misleadingly named, and is still the default in a lot of tooling, tutorials, and older CREATE TABLE boilerplate as of 2026.

The result: if you're on MySQL and your column charset says utf8 (not utf8mb4), you do not have full UTF-8 support, no matter what the name implies.

Byte structure

Charset Max bytes/char What it actually covers
utf8 (MySQL legacy) 3 bytes Basic Multilingual Plane only — misses emoji, many historic/rare scripts
utf8mb4 (MySQL, correct) 4 bytes Full Unicode, matches the real UTF-8 spec

The "mb4" literally stands for "max bytes 4" — MySQL's naming convention, not a Unicode term. Nowhere else in the software world does "utf8" mean anything other than full UTF-8; this mismatch is entirely MySQL's own historical baggage.

Why your text is garbled (symptom → cause)

Symptom Likely cause
Emoji turn into ? or ???? when saved Column or connection charset is utf8, not utf8mb4 — the 4-byte emoji gets truncated or rejected
INSERT fails with Incorrect string value: '\xF0\x9F...' Same root cause — MySQL is explicitly rejecting the 4-byte sequence because the column can't store it
Data looks fine in phpMyAdmin/CLI but breaks in the app Connection charset mismatch — your app's DB connection may be negotiating utf8 even if the table is utf8mb4
Some emoji work, others don't Emoji requiring surrogate pairs / rare 4-byte code points vs. simpler 3-byte-representable symbols — inconsistent behavior is a strong signal of a mixed utf8/utf8mb4 setup somewhere in the stack
Truncated or silently missing characters mid-string on old rows Data was originally inserted under utf8, silently dropping unsupported characters at insert time — often invisible until someone audits older rows

Compatibility & gotchas

  • It's not just the column. Charset can be set at the server, database, table, column, and connection level — all four have to line up. A utf8mb4 column behind a connection that's still negotiating utf8 will still corrupt data.
  • Collation matters too. utf8mb4_general_ci is faster but less linguistically accurate; utf8mb4_unicode_ci and newer utf8mb4_0900_ai_ci (MySQL 8+) are more correct for sorting/comparison but slightly slower. Pick based on whether you need precise multilingual sort order.
  • Index length limits under utf8mb4. Because each character can now take up to 4 bytes instead of 3, VARCHAR columns with indexes near MySQL's old 767-byte index prefix limit (InnoDB, pre-5.7 defaults) can suddenly fail to create when converted from utf8 to utf8mb4 — the same character count now needs more bytes. Enabling innodb_large_prefix / using the DYNAMIC or COMPRESSED row format resolves this on older MySQL versions; MySQL 8+ defaults are more forgiving.
  • Laravel-specific: as of recent Laravel versions, utf8mb4 with utf8mb4_unicode_ci (or _0900_ai_ci on MySQL 8) is the default in config/database.php — but older projects migrated from earlier Laravel versions, or hand-rolled schemas, often still carry legacy utf8 definitions that were never revisited.
  • This is MySQL/MariaDB-specific. PostgreSQL's UTF8 charset has always been full, correct UTF-8 — this whole gotcha doesn't exist there.

Fixing it

  1. Change the database default (new tables only): ALTER DATABASE your_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
  2. Convert an existing table: ALTER TABLE your_table CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; — this rewrites every column's charset in one pass and is generally safer than converting column-by-column.
  3. Fix the connection charset — in Laravel's config/database.php, confirm 'charset' => 'utf8mb4' and 'collation' => 'utf8mb4_unicode_ci' (or _0900_ai_ci) are set on the MySQL connection array; for raw PDO connections, ensure the DSN or SET NAMES utf8mb4 is issued on connect.
  4. Check index prefix limits if conversion fails on a VARCHAR with an index — either shorten the indexed prefix length or confirm innodb_large_prefix is enabled (default in MySQL 8+).
  5. Audit already-corrupted rows. Data that was silently truncated under utf8 is already lost — converting the charset going forward won't recover characters that were dropped at insert time. If this matters, you'll need to re-import from a source that still has the original data.

Comparison

MySQL utf8 MySQL utf8mb4 True UTF-8 (the actual spec)
Max bytes/char 3 4 4
Covers emoji No Yes Yes
Covers full Unicode No (BMP only) Yes Yes
Recommended for new projects No Yes

FAQ

Why does MySQL even have a "utf8" that isn't real UTF-8?

Historical accident. When MySQL added utf8 support, 3 bytes covered nearly everything in practical use at the time. Unicode later expanded (emoji being the most visible addition) beyond what 3 bytes can represent, but MySQL couldn't silently change the existing utf8 charset's byte width without risking data corruption on every table already using it — so they added utf8mb4 as a new, correctly-sized charset instead and left the old one as-is.

Do I need to convert my whole database, or just specific columns?

Whole database is cleaner if you're doing it — run the ALTER DATABASE first so all new tables default correctly, then ALTER TABLE ... CONVERT TO CHARACTER SET utf8mb4 on each existing table. Mixing utf8 and utf8mb4 columns within the same schema works but adds ongoing risk of exactly this kind of mismatch bug resurfacing.

Will converting to utf8mb4 break my existing data?

Converting the charset itself is safe for data that's already valid utf8 — it's a superset, so nothing already stored gets corrupted by the conversion. The risk is only with index length limits on VARCHAR/CHAR columns with existing indexes, which may need adjusting as described above.

How do I know if my app is actually affected right now?

Try inserting an emoji (🎉) into a text field and see what comes back. If it saves as ?, gets truncated, or throws an "incorrect string value" error, you've got a utf8/utf8mb4 mismatch somewhere in your stack — check column charset, table charset, and connection charset in that order.

Fun fact

The 767-byte InnoDB index prefix limit that trips people up during utf8utf8mb4 migrations exists because InnoDB originally capped index key prefixes at 767 bytes total — under 3-byte utf8, that's roughly 255 characters, a suspiciously familiar-looking number to anyone who's worked with old VARCHAR(255) conventions. Under 4-byte utf8mb4, the same 767-byte limit only covers about 191 characters, which is exactly why so many old VARCHAR(255) indexed columns started failing conversion until larger prefixes were enabled by default.