Unicode bugs often appear far from their source: a form submits correctly, an API changes a character, and a database later returns mojibake or the replacement character �. This practical checklist helps you isolate the failing boundary and verify text safely across HTML, HTTP, JSON, JavaScript, SQL, databases, and browser interfaces.
Overview
Unicode is the system for representing characters from many writing systems, while UTF-8 is a variable-length encoding used to turn those characters into bytes. A web application can handle Unicode correctly only when each layer agrees about how text is represented and decoded.
Most encoding failures occur at a boundary where text becomes bytes or bytes become text. Typical boundaries include a browser form submission, an HTTP request or response, a JSON serializer, a database connection, a file import, or a message queue. The visible symptom may be a useful clue, but it does not always identify the faulty layer.
Common symptoms include:
- Mojibake: text such as
éappears whereéwas expected, usually because UTF-8 bytes were decoded with a different character encoding. - Replacement characters:
�appears after a decoder encounters invalid or incomplete byte sequences. Once the original bytes have been replaced, recovery may not be possible from the displayed text alone. - Broken emoji: an emoji is missing, rendered as a box, split into unexpected symbols, or truncated in storage or display.
- Unexpected truncation: a field cuts off a character or rejects input because a byte limit, character limit, or application rule was applied at the wrong layer.
- Inconsistent comparisons: visually identical strings do not match because they use different Unicode normalization forms.
Start by preserving the original input and creating a small test set. Include plain ASCII, accented text such as café, non-Latin scripts, an emoji, and a character that uses combining marks. You can inspect code points and UTF-8 bytes with a browser-based Unicode utility; the Unicode code point and UTF-8 bytes guide explains what to record.
Checklist by scenario
When the page displays garbled text
- Inspect the response headers and confirm the media type declares the intended character set, commonly with
Content-Type: text/html; charset=UTF-8. - Check the document's HTML metadata, such as
<meta charset="utf-8">, and place it early enough for the browser to use it while parsing. - Open the browser's network and document tools to compare the declared charset with the actual response bytes.
- Determine whether the corruption already exists in the response or is introduced by client-side JavaScript after loading.
- Test the same value in a minimal page. If it works there, compare templating, escaping, font, and rendering logic in the full application.
A font problem can resemble an encoding problem. If the code point and bytes are correct but the glyph appears as a box, investigate font coverage, fallback behavior, or rendering support rather than changing the encoding.
When an API returns damaged JSON
- Capture the raw response before parsing it. Compare the raw bytes with the parsed string.
- Confirm that the response uses an appropriate JSON media type and declares UTF-8 where your stack expects an explicit charset.
- Check the server's JSON serializer and the client library. Avoid manually concatenating JSON strings, which can introduce escaping and encoding errors.
- Inspect request headers and body handling separately. A server may decode a request incorrectly even when it produces a correct response.
- Repeat the test with escaped and unescaped Unicode where the client permits both. JSON escaping changes representation, not the underlying character value.
For a broader validation routine, see how to validate Unicode in JSON APIs and web forms. Treat a replacement character in an API payload as a data-integrity warning, not merely a display defect.
When form input fails or changes
- Log the value at the browser, HTTP request, server parser, validation layer, and persistence layer. Redact sensitive data, but preserve enough test input to compare each stage.
- Check whether client-side validation counts JavaScript string units, Unicode code points, grapheme clusters, or encoded bytes. These are different measures.
- Review URL encoding for query parameters and form submissions. Non-ASCII characters must be percent-encoded correctly when they travel in a URL.
- Check whether normalization is applied consistently before validation, comparison, search, or storage.
- Test pasted text, mobile input, composed characters, and emoji sequences rather than relying on one typed example.
The guide to encoding and decoding URLs with non-ASCII characters is useful when the issue appears only in links, redirects, query strings, or route parameters.
When data is corrupted in SQL or a database
- Check the database, table, column, connection, driver, and client settings. A Unicode-capable column does not guarantee that the connection is decoding bytes correctly.
- Compare the value immediately before the insert with the value returned by a direct query.
- Inspect limits in both characters and bytes. A multi-byte UTF-8 character can occupy more bytes than an ASCII character.
- Review import and export tools for assumed encodings, especially when files pass through spreadsheets, command-line utilities, or older integrations.
- Do not repair records until you have identified whether the stored bytes, the decoder, or only the display layer is at fault.
If the original bytes were decoded incorrectly and then saved, a later encoding change may not restore the source text. Make a backup and test any repair transformation on a copy.
What to double-check
Bytes, code points, and visible characters
Do not use “character length” as if it had one universal meaning. A code point is a Unicode value, UTF-8 represents that value with one or more bytes, and a user-perceived character may contain several code points joined by combining marks or emoji sequences. A JavaScript string length, a database character limit, and a rendered grapheme count can therefore differ.
Record a failing sample in at least three forms: the visible string, its code points, and its UTF-8 bytes. This quickly shows whether the problem is in the data, the conversion, or the display. The normalization guide covers why composed and decomposed forms can look identical while comparing differently.
Normalization and comparison
Choose a normalization policy for fields that need reliable equality, search, or deduplication. NFC is a common choice when preserving canonical Unicode representation, while compatibility normalization can change distinctions that may matter in identifiers or user content. Do not normalize blindly before understanding the field's purpose.
Security and identifiers
Visually similar characters can create confusing names, URLs, or account identifiers. For security-sensitive values, consider confusable detection, script restrictions, and a documented policy for allowed characters. A Unicode confusables checklist can help distinguish ordinary multilingual content from identifier risks.
Logging and observability
Logs should make encoding failures diagnosable without exposing private data. Where appropriate, record a redacted sample, code points, byte length, source boundary, and request or job identifier. Avoid logging raw authentication tokens, personal information, or untrusted text without an appropriate review.
Common mistakes
- Changing the charset declaration without converting the data: a header tells a decoder how to interpret bytes; it does not transform incorrect bytes into correct ones.
- Applying URL encoding to ordinary text: percent encoding belongs to a URL component or transport context. It should not be used as a general database or display format.
- Assuming JSON escaping fixes encoding: escaping may make transport safer, but it cannot restore text that was already decoded incorrectly.
- Counting UTF-16 units as user-visible characters: some characters, including many emoji, occupy more than one JavaScript string unit.
- Normalizing only one side of a comparison: normalize according to a consistent field policy before comparing or indexing.
- Repairing mojibake by trial and error: a reverse conversion can make a sample look better while damaging already-correct records.
- Testing only ASCII: ASCII passes through many broken pipelines. Include accented, multilingual, combining, and emoji samples in automated tests.
When to revisit
Revisit this checklist before a release that changes database drivers, API clients, serializers, import jobs, hosting configuration, or frontend input handling. Run it when adding a new language, accepting user-generated content, changing field limits, migrating data, or moving between environments.
It is also worth repeating before seasonal planning or campaign cycles when new names, locations, product labels, or customer data may enter the system. Review it after upgrading a framework or browser-facing library, especially if string length, URL routing, validation, or database connectivity changed.
Keep a small Unicode regression suite in the project. Include the same known samples at every boundary and assert the expected value, code points, normalization behavior, byte length where relevant, and round-trip result. When a test fails, stop at the first boundary where the representation changes. That habit turns a vague “encoding issue” into a specific conversion or storage defect, making the fix safer and easier to verify.
For a release-focused version of this process, use the Unicode text QA checklist and update the test samples whenever your supported languages, inputs, or workflows change.