UTF-8 a été inventé par Kenneth Thompson lors d'un dîner avec Rob Pike aux alentours de septembre 1992. Il a été immédiatement utilisé dans le système d'exploitation Plan 9 sur lequel ils travaillaient. Une contrainte à résoudre était de coder les caractères nul et '/' comme en ASCII et qu'aucun octet codant un autre caractère n'ait le même code. Ainsi les systèmes d'exploitation UNIX pouvaient continuer à rechercher ces deux caractères dans une chaîne sans adaptation logicielle.
Le codage original était alors appelé FSS-UTF (File-System Safe Unicode Transform Format) et était destiné à remplacer le codage multi-octets UTF-1 initialement proposé par l'ISO 1646. Ce codage initialement permissif, permettait plusieurs représentations binaires pour le même caractère (cela a été interdit dans la version normalisée dans la RFC publiée par le Consortium X/Open, et approuvé par Kenneth Thompson).
De plus il pouvait (dans une version préliminaire non retenue) coder tous les caractères dont la valeur de point de code comprenait jusqu'à 32 bits en définissant un huitième type d'octet (dans des séquences comprenant jusqu'à 6 octets), au lieu des 7 types d’octets finalement retenus pour ne coder (dans des séquences comprenant aussi jusqu'à 6 octets) que les points de code jusqu'à 31 bits dans la version initiale d'UTF-8 (publiée par le Consortium X/Open sous le nom FSS-UTF, puis proposé par le comité technique d’ISO 10646 comme la proposition « UTF-2 » alors encore en concurrence avec la proposition « UTF-1 », jusqu'à ce que la proposition UTF-2 soit retenue et adopte le nom UTF-8 déjà retenu et utilisé dans X/Open et Plan 9).
Ce codage UTF-8 a été restreint encore lorsque Unicode et ISO 10636 ont convenu de n'allouer des caractères que dans les 17 premiers plans afin de maintenir indéfiniment la compatibilité avec UTF-16 (sans devoir le modifier), en restreignant les séquences jusqu'à 4 octets seulement et en n’utilisant que les 5 premiers des 7 types d'octets (ce qui a nécessité de définir comme invalides de nouvelles valeurs d’octet et certaines séquences d'octets pourtant valides individuellement).
Toutefois, des variantes d’UTF-8 (basées sur les possibilités de codage de la version initiale non restreinte) ont continué à être utilisées (notamment dans l’implémentation de la sérialisation des chaînes Java) pour permettre de coder sous forme d'un échappement multioctets certains caractères ASCII réservés normalement codés sur un seul octet (par exemple le caractère nul).
De plus, certains systèmes utilisent des chaînes de caractères non restreints : par exemple, Java (et d’autres langages y compris des bibilothèques de manipulation de chaînes en C, PHP, Perl, etc...) représentent les caractères avec des unités de codage sur 16 bits (ce qui permet de stocker les chaînes en utilisant le codage UTF-16, mais sans les contraintes de validité imposées par UTF-16 concernant les valeurs interdites et l'appariement dans l’ordre des « demi-points de code » ou surrogates) ; dans ce cas, les unités de codage sont traitées comme des valeurs binaires et il est nécessaire de les sérialiser de façon individuelle (indépendamment de leur interprétation possible comme caractères ou comme demi-points de code). Dans ce cas, chaque unité de codage 16 bits qui représente un « caractère » (non-contraint) est sérialisé sous forme de séquences comprenant jusqu'à 3 octets chacune, et certains octets interdits par l’implémentation (par exemple les caractères nuls ou la barre de fraction « / » dans un système de fichiers ou d’autres caractères codés sur un octet dans d’autres protocoles) sont codés sous forme de séquences d’échappement à deux octets dont aucun n’est nul, en utilisant simplement le principe de codage de la première spécification de FSS-UTF (avant celle qui a été retenue par le Consortium X/Open dans sa RFC initiale où ces échappements étaient spécifiquement interdits et le sont restés).
Avant l’adoption de la proposition UTF-2 retenue pour UTF-8, il a également existé une variante UTF-1, où les codages multiples étaient impossibles, mais nécessitait un codage/décodage plus difficile devant prendre en compte la position de chaque octet et utilisant un certain nombre de valeurs « magiques ».
Ces variantes ne doivent pas être appelées « UTF-8 ».
Une de ces variantes non standards a fait cependant l’objet d’une standardisation ultérieure (en tant qu’alternative à UTF-16 et utilisant des paires de demi-codets codés chacun sur 3 octets) : voir CESU-8.
Par exemple les API d’intégration des machines virtuelles Java (pour JNI, Java Native Interface ou pour la sérialisation des classes précompilées), qui permettent d’échanger les chaînes Java non contraintes sous forme de séquences d’octets (afin de les manipuler, utiliser ou produire par du code natif, ou pour le stockage sous forme de fichier natif codés en suites d’octets) sont suffixées par "UTFChars" ou "UTF", mais ce codage propre à Java n’est pas UTF-8 (La documentation de Sun la désigne comme modified UTF, mais certains documents plus anciens relatifs à JNI désignent encore ce codage incorrectement sous le nom UTF-8, ce qui a produit des anomalies de comportement de certaines bibliothèques natives JNI, notamment avec les API systèmes d’anciennes plateformes natives qui ne supportent pas nativement les codages de caractères sur plus de 8 bits), car :
En conséquence,