blog

Unicode escapes in code: JavaScript, CSS, Rust, Perl, Java

koboshi · Co-founder

Try it yourself

Everything in this article runs live in the converter. Paste your own text and watch every notation update at once.

Open the Unicode converter

Paste \u00E9 into the Unicode converter and the Characters panel reads é. Paste \x{1F600} into the Perl panel and it reads 😀. Both are Unicode escapes: a way to write a code point as plain ASCII and let the language turn the number into text. The idea is the same everywhere, but each language picked its own syntax and its own edge cases. Here are the five dialects you will meet most often, each checked against its language specification.

JavaScript: three spellings, two different things

JavaScript has three escapes. \u00E9 takes exactly four hex digits, and ECMA-262 defines its value as one UTF-16 code unit with that numeric value. \x41 takes exactly two hex digits and yields one code unit in the range 0x00 to 0xFF, a byte-era leftover that covers only Latin-1. \u{1F600}, added in ES2015, takes a full code point up to U+10FFFF and encodes it as UTF-16: one code unit inside the Basic Multilingual Plane, a surrogate pair above it.

javascript
"\u00E9"            // é
"\x41"              // A
"\u{1F600}"         // 😀, one code point
"\uD83D\uDE00"      // the same emoji as two code units
"\u{1F600}".length  // 2, because length counts code units

The gotcha is string.length. ECMA-262 defines a String as a sequence of 16-bit unsigned integers, usually UTF-16 code units, and length is the count of those elements, so one emoji reports a length of 2. Template literals process the same escapes as string literals, with one exception: a tagged template tolerates even malformed escapes, producing an undefined cooked value while the raw text stays available. That is what lets String.raw`\u{1F600}` return the nine source characters verbatim.

CSS: up to six digits, and the space that vanishes

CSS Syntax Level 3 defines an escape as a backslash followed by one to six hex digits. If whitespace follows the digits, exactly one whitespace code point is consumed as a terminator, so \E9 , \00E9 and \0000E9 all produce é (U+00E9 LATIN SMALL LETTER E WITH ACUTE). Supplementary characters fit as well: \1F600 is 😀. The classic use is the content property, where an escape keeps a stylesheet pure ASCII.

css
.icon::before { content: "\1F600 "; }    /* 😀, space ends the escape */
.icon::before { content: "\01F600 "; }   /* identical, padded to six digits */
.note::before { content: "\E9 coutez"; } /* renders écoutez */

The vanishing space is the gotcha. In \E9 coutez the space terminates the escape and is eaten, so the rendered text is écoutez, not é coutez. The converter's CSS panel applies the same rule in reverse and writes every escape with a trailing space. One more spec detail: an escape that evaluates to zero, to a surrogate, or above U+10FFFF does not throw an error. It becomes U+FFFD REPLACEMENT CHARACTER.

Rust: braces for Unicode, \x for ASCII only

Rust's \u{...} takes up to six hex digits in braces, and the value must be a Unicode scalar value, so \u{D83D} is a compile error rather than a lone lead surrogate. \xNN in a string or char literal takes exactly two hex digits and stops at 0x7F. The Rust Reference explains why: above 0x7F it is ambiguous whether the number means a code point or a byte. Byte strings (b"...") lift the cap, and there \xE9 really is the single byte 0xE9.

rust
let e = '\u{E9}';        // é
let emoji = '\u{1F600}'; // 😀
let a = '\x41';           // A
// '\xE9'      error: out of range hex escape, 0x7F is the ceiling
// '\u{D83D}'   error: invalid unicode character escape (a surrogate)
"é".len();                // 2, len() counts UTF-8 bytes
"é".chars().count();      // 1, the scalar value count

So the gotcha is len(): it returns bytes, because Rust strings are UTF-8, and chars().count() is the character count. Muscle memory from JavaScript's length picks the wrong one here.

Perl: braces on \x, and a pragma for your source file

In double-quoted strings and regexes, \x{1F600} takes a hex number of any length in braces and denotes the code point. Without braces, \x reads exactly two hex digits. Perl also offers the explicit \N{U+1F600}, and \N{GRINNING FACE} uses the character's official name.

perl
my $e = "\x{E9}";          # é
my $emoji = "\x{1F600}";   # 😀
my $a = "\x41";            # A, two digits without braces
my $also = "\N{U+1F600}";  # 😀, the explicit code point form

The gotcha is not the escape but the source file. Perl reads your script as bytes unless you write use utf8;. A literal é saved in a UTF-8 file without the pragma is read as two characters, Ã followed by ©, while \x{E9} works either way. The pragma declares that the script itself is UTF-8, and that is all it does.

Java: \uXXXX everywhere, comments included

Java has exactly one form: \uXXXX, four hex digits, one UTF-16 code unit (extra u characters are allowed, so \uu00E9 is legal). A supplementary code point needs two consecutive escapes, \uD83D\uDE00 for 😀. The unusual part is timing. JLS section 3.2 makes Unicode escape translation the first step of lexical translation, before comments, tokens and string literals are even recognized, so the escapes work in identifiers and comments too.

java
String e = "\u00E9";            // é
String emoji = "\uD83D\uDE00";  // 😀 takes two escapes
char lf = '\n';                  // the right way to write a line feed
// char bad = '\u000A';          // compile-time error

That early pass causes the most famous escape bug in any mainstream language. '\u000A' looks like a char literal holding a line feed, but translation step 1 rewrites it into a literal containing a real line break, which is illegal, and the JLS explicitly tells you to write '\n' instead. The same trap fires in comments: // never write \u000A here ends the comment at the escape, because the line feed it becomes terminates the comment, and whatever follows on the next source line is suddenly live code.

Every escape in this article names the same thing: a code point. What differs is the unit the language stores it in, and that difference is where the bugs live.

A checklist for the next escape you paste

  • Above U+FFFF, use the braced form in JavaScript, Rust and Perl. In Java write two \uXXXX escapes, in CSS write up to six digits.
  • \x never means the same thing twice: two hex digits, capped at 0xFF in JavaScript and at 0x7F in Rust strings.
  • When length matters, check the unit: UTF-16 code units in JavaScript and Java, UTF-8 bytes in Rust's len().
  • Paste any of these escapes into the converter's main input and the Characters panel shows what they decode to. The JavaScript, Rust, Perl and CSS panels generate each dialect for you.
  • In Java source, never write \u000A. Write \n.

If your target is markup rather than source code, the same code points travel through a different syntax entirely, numeric character references like é, which is covered in Unicode to HTML entities.

Try it yourself

Everything in this article runs live in the converter. Paste your own text and watch every notation update at once.

Open the Unicode converter