Developer tools · URL encoder & decoder
URIError: URI malformed — why decodeURIComponent throws and how to fix it
· How it works
url-encoding javascript error-handling
decodeURIComponent throws when a percent sign is not followed by two hex digits, or when the decoded bytes are not valid UTF-8. This post shows the inputs that trigger it and how to decode defensively.
The percent sign that crashed — why "100% off" breaks decodeURIComponent
A form collects discount code "100% off". JavaScript passes this to decodeURIComponent in a URL decoder. The function throws URIError: URI malformed. The percent sign is not followed by two hexadecimal digits. This violates percent-encoding rules completely. decodeURIComponent expects every % to start a triplet like %20 or %C3. A lone % is a syntax error that stops execution immediately and throws an error.
Catching this error safely prevents application crashes entirely. URLs arrive from user input, redirects, QR codes and emails. Typos happen frequently. A crash report with URIError tells you where to investigate quickly. Defensive decoding keeps applications running and error logs become useful for debugging.
Two kinds of failure — malformed hex escapes and invalid UTF-8 byte sequences
decodeURIComponent throws in exactly two situations. First: malformed escape sequence. A percent not followed by two hex digits (0-9, A-F, a-f). Examples: %ZZ, %2, %2g. Second: valid triplet like %E9 decoding to invalid UTF-8 bytes. The first is format error. The second is semantic error. Both throw and stop execution immediately.
UTF-8 has strict rules about byte sequences. Bytes 0x80–0xFF appear only in multi-byte sequences. A single %E9 cannot be valid UTF-8 alone. This orphaned byte triggers error. Format errors are obvious. Semantic errors are subtle but equally real. Both cases require try/catch handling in production code.
Legacy single-byte encoding — when %E9 alone throws but %C3%A9 survives
Confusion stems from web standards history. Old pages used Latin-1 instead of UTF-8. In Latin-1, %E9 represented é. Modern browsers use UTF-8 exclusively. UTF-8 encodes é as %C3%A9. Modern decoders expect UTF-8 and reject %E9 as malformed. This is correct behavior. The error signals a problem with source data.
Modern consensus: UTF-8 everywhere. The URL Standard specifies UTF-8. All current browsers use UTF-8. If you encounter %E9 from old systems, catch the error and fallback to raw string. Do not decode as Latin-1 in modern code. Investigate where the data originated from.
Three inputs, three error messages — how engines differ on the same broken string
Browser engines reject malformed input consistently but word errors differently. Chrome reports "URI malformed". Firefox reports "malformed URI sequence". Safari reports "cannot convert undefined to object". All three engines reject identical input. Exact message wording is not standardized across different engines or versions. Never rely on error text to guide code logic.
Never string-match an error message for program decisions. Always catch URIError by type. The decodeUrl function wraps decodeURIComponent and provides consistent code INVALID_PERCENT_ENCODING. This names the exact problem position. It works across runtimes because it does not depend on engine wording variations. This approach is more reliable and maintainable.
Reading the exception safely — try/catch, validation, and fallback patterns
Simplest defensive pattern: wrap decodeURIComponent in try/catch. If it throws, use raw string or replacement character. This prevents malformed input from crashing. For query values, show the URL-encoded form. For user-facing text, insert replacement character. This keeps bad input from breaking applications and maintains stability.
Pre-validate with regular expressions for speed and safety. Check that input contains only valid %XX triplets before decoding. The pattern /%[0-9A-Fa-f]{2}/g catches valid escapes; anything unmatched is invalid. Format errors fail fast on obvious garbage. UTF-8 errors still need try/catch. Together this provides comprehensive defensive protection against errors.
Silent failures hide bugs — why blind decoding is as risky as broken encoding
Subtle risk: the decoder does not throw but silently produces wrong text. Old code using deprecated unescape leaves invalid UTF-8 in memory. Text looks fine on screen until reaching systems that validate UTF-8 strictly. Modern code throws rather than corrupt silently. An exception is clearer and safer than silent data corruption spreading downstream.
Assume user input is malformed. Always wrap calls. Log errors with original input for debugging. Never assume every % is valid. Typos and truncation create incomplete escapes. Treat as data errors, not logic errors. Defensive code survives bad input gracefully and keeps systems reliable.
What real tools skip — server-side framework behaviour and error recovery
Server frameworks handle malformed encoding more leniently than browsers. Ruby, Python and PHP offer configuration for handling invalid escapes in URLs. Some substitute replacement characters automatically. Others drop bytes silently. Some throw exceptions like JavaScript does. Actual behavior varies by framework and configuration settings chosen by developers.
This article covers browser JavaScript behavior only. If values arrive from server APIs, the server already decoded or skipped errors before sending. Servers can be more forgiving than clients. When writing API contracts, specify whether values are raw or pre-decoded. URL query strings should arrive percent-encoded; JSON can arrive pre-decoded.
Validate early — using URL encoder & decoder to check suspicious strings first
Before passing suspicious URLs to decodeURIComponent, paste into URL encoder & decoder. The tool shows exact encoding, spots malformed escapes and explains errors without crashing your application. Test with %ZZ, %E9 and 100% to see different failures and their exact error messages. This takes seconds and builds confidence.
Validate early, catch errors gracefully and log what broke. Defensive decoder plus testing tool keeps applications running and debuggable. URL encoder & decoder turns "URI malformed" into actionable information that you can use immediately. Apply this pattern to your own decoders for resilience and maintainability in production environments.