Choosing between listings and minted for source code in LaTeX is not a question of taste in colour schemes. listings does its syntax highlighting with TeX macros alone, and what it knows about a language is a hand-written list of which words count as keywords. minted hands that job wholesale to Pygments, a lexer written in Python, so its colouring is in a different class — but it has to step outside LaTeX to get there. Quality or portability: that was the trade for years, until minted 3 rewrote the terms of it. This page sorts out the two packages from where things now stand.
The difference between listings and minted
The difference comes down to one thing: who does the highlighting. listings is self-contained in pure LaTeX macros, so \usepackage{listings} simply works — on Overleaf, or in a computer lab whose configuration you cannot touch. minted calls an external program and reads back the analysis, which wins on accuracy but makes the availability of that program a precondition. Choosing between them is, in the end, a prediction about where your document will eventually be compiled.
That “hand-written list” is not a metaphor. The language definitions of listings live in three files, lstlang1.sty through lstlang3.sty, and their contents are a run of entries like \lst@definelanguage{ACSL}[90]{Fortran}{morekeywords={algorithm,cinterval,...}} — keywords separated by commas. Counting the copy shipped with TeX Live 2024 gives about 95 languages. Pygments, by contrast, is an independent lexing library developed since 2006 by Georg Brandl and others; running pygmentize -L lexers on Pygments 2.19 lists 597 lexers. The gap in numbers matters less than the gap in principle: one is a list of words, the other a machine that cuts a stream into tokens according to a grammar.
| listings | minted | |
|---|---|---|
highlighting | Approximation from a keyword list | Real lexing by Pygments |
external tools | none (pure LaTeX macros) | Pygments; minted 3 bundles latexminted |
-shell-escape | not needed | required by minted 2; not needed by minted 3 on TeX Live 2025 or later |
languages | about 95 (as shipped with TeX Live 2024) | 597 lexers (Pygments 2.19) |
UTF-8 | fatal error under pdfLaTeX | characters silently dropped under pdfLaTeX (a PDF is still produced) |
listings basics: the lstlisting environment and \lstinputlisting
There are only three entry points. Write code directly in the document with the lstlisting environment, pull an external file in verbatim with \lstinputlisting{sample.py}, and drop a short fragment into running text with \lstinline. Appearance is then set once in the preamble with \lstset{...} rather than repeated at every call site. listings offers well over a hundred options, but the dozen or so in the example below are all you normally touch.
\usepackage{listings}
\usepackage{xcolor} % needed for the \color{...} styles below
\lstset{
language=Python,
basicstyle=\ttfamily\small, % base font for the code
keywordstyle=\color{blue}\bfseries,
commentstyle=\color{teal}\itshape,
stringstyle=\color{red!60!black},
numbers=left, % line numbers in the left margin
numberstyle=\tiny\color{gray},
frame=single, % draw a thin frame around the block
breaklines=true, % wrap lines that are too long
showstringspaces=false,
tabsize=2,
}
\begin{lstlisting}[caption={Factorial, computed recursively}, label=lst:fact]
def factorial(n):
if n <= 1:
return 1
return n * factorial(n - 1)
\end{lstlisting}
% pull in lines 37-45 of an external file, without line numbers
\lstinputlisting[language=C, firstline=37, lastline=45, numbers=none]{sample.c}The \lstset keys are easier to remember grouped by role. Typeface is basicstyle (\ttfamily\small is the usual choice). Semantic colour is keywordstyle, commentstyle and stringstyle. Surrounding decoration is numbers=left (line numbers on the left, formatted by numberstyle) and frame=single (a border). breaklines=true wraps lines too long for the measure, and forgetting it is how code ends up running straight through the right margin — the most commonly hit pitfall in practice. Supply caption= and label= and the block becomes a numbered listing on a par with figures and tables, referable with \ref{lst:fact}.
Preamble settings can be overridden at any individual block inside [ ]. Writing \begin{lstlisting}[language=C, numbers=none] makes that one block C, without line numbers. For external files, firstline= and lastline= come into play as well, and \lstinputlisting[firstline=37, lastline=45]{sample.c} slices out just the lines you need — which pays off in practice, because nothing is copied into the document and fixing the original file updates the text automatically. Inline code follows the \verb idiom: pick any character as the delimiter, as in \lstinline|while (i < n)|.
Why CJK in a listing stops with Invalid UTF-8 byte sequence
This is not a package problem but an engine problem. Compile with pdfLaTeX and a Han character or Hangul syllable inside your code becomes, under listings, the fatal ! LaTeX Error: Invalid UTF-8 byte sequence — and no PDF is produced at all. Switching to minted does not fix it: minted reports ! LaTeX Error: Unicode character and then quietly builds a PDF with the character missing. Both symptoms come from the same root, namely that multibyte characters do not fit pdfTeX's assumption that one byte is one character.
The widely circulated advice to load listingsutf8 does not help with CJK. The package README says why in as many words: the workaround only applies when a one-byte encoding exists that the file can be converted to, and it only affects \lstinputlisting. Accented letters in European languages can be dropped to latin1, but no one-byte encoding holds Han characters, kana or Hangul, so there is nowhere to convert to. Run \lstinputlisting[inputencoding=utf8/latin1]{sample.py} in earnest and the error does disappear — along with the characters themselves, which vanish from the output. It is a nasty failure mode precisely because a silent build looks like success.
The real fix is to change engine. Typeset with XeLaTeX or LuaLaTeX — both treat their input as Unicode from the outset — and listings and minted alike will pass code containing Japanese comments straight through. All that remains is for the monospaced font to contain the characters, which you select with \setmonofont from fontspec. One caution here: a font usually covers only its own language. Setting Simplified Chinese or Hangul in a Japanese font produces a row of Missing character warnings and those characters silently disappear. For code with several scripts in it, pick a face that covers all of them. If you only need a handful of accented European letters, the classic route still works under pdfLaTeX: teach them one at a time with \lstset{literate={é}{{\'e}}1}.
% Compile this with xelatex or lualatex, not pdflatex.
% A monospaced face that actually covers the script you use.
\usepackage{fontspec}
\setmonofont{Noto Sans Mono CJK JP}
\usepackage{listings}
\lstset{basicstyle=\ttfamily\small}
\begin{lstlisting}[language=Python]
def factorial(n):
# a comment written in your own language survives here
return 1 if n <= 1 else n * factorial(n - 1)
\end{lstlisting}minted basics: \begin{minted}{python} and \inputminted
The shape is much the same as listings, with one difference: the language is a mandatory argument. You put its name in the environment argument, as in \begin{minted}{python}; for an external file it is \inputminted{python}{sample.py}, and for a fragment in running text \mintinline{python}{print("hi")}. The language cannot be omitted because Pygments has to be handed exactly one lexer before any analysis can begin. There is no listings-style “set it once in the preamble and leave it out thereafter”.
\usepackage{minted}
\usemintedstyle{monokai} % one colour theme for the whole document
% \setminted{style=monokai, linenos, fontsize=\small} % broader defaults
\begin{minted}[linenos, bgcolor=black!90, fontsize=\small]{python}
def factorial(n):
if n <= 1:
return 1
return n * factorial(n - 1)
\end{minted}
\mint{python}|print("Hello!")| % one line, no environment
\mintinline{python}{print("Hello!")} % inside running text
\inputminted[linenos]{python}{sample.py} % a whole external file
% "text" turns highlighting off without leaving minted
\begin{minted}{text}
plain output, no keywords coloured
\end{minted}Options go in [ ] right after the environment name, as key=value pairs. The common ones are linenos for line numbers, style= to pick a Pygments colour scheme, bgcolor= for a background, and fontsize=. Apply one scheme document-wide with \usemintedstyle{monokai}, or fix a batch of defaults with \setminted{style=monokai, linenos}. For a language Pygments does not know, or a block you deliberately want left plain, pass text as the language. One warning: \mint is not the inline command — it merely saves you writing an environment around a single line of code. To blend code into running text, always use \mintinline.
Why minted needs -shell-escape, and when it stopped needing it
minted starts an external program in the middle of typesetting, so it needs shell escape — the permission that lets LaTeX run external commands. Without it the run halts with ! Package minted Error: You must invoke LaTeX with the -shell-escape flag. Compile with -shell-escape under pdfLaTeX, or -enable-write18 under MiKTeX.
pdflatex -shell-escape document.tex
xelatex -shell-escape document.tex
# MiKTeX uses the older spelling
pdflatex -enable-write18 document.texThis is the part minted 3 changed. Previously you had to install Python and Pygments yourself and then open up unrestricted shell escape, and that was the real substance of the quality-versus-portability choice this page opened with. minted 3 gathers the Python side into a dedicated executable called latexminted and ships it inside TeX distributions as a Python wheel. Its author, Geoffrey M. Poore, describes it as designed to be compatible with LaTeX's security requirements for restricted shell escape executables. The upshot: on TeX Live 2025, latexminted is on the restricted shell escape allowlist and you can compile without -shell-escape at all — and the separate Pygments installation is gone too.
If your local installation is older, though, the story is different. TeX Live 2024 ships minted 2.9, dated December 2023, and that version still refuses to run without -shell-escape. Which world you are in is easy to tell: either the error above appears or it does not. And enabling shell escape means granting that document permission to run arbitrary external commands. Never run a .tex file of unknown provenance with -shell-escape. It is the same reason conference and publisher submission systems sometimes forbid shell escape outright, so it is worth checking once, before you submit, that your document still builds without it.
Calling an external process makes minted slower to compile than listings. What offsets this is caching: minted stores each highlighted fragment in a working directory and does not call Pygments again unless the code changes. Typesetting document.tex on TeX Live 2024 here creates a directory named _minted-document/, holding .pygtex files named after a hash of the code fragment. That mechanism is why the second and later runs are noticeably faster. Caching can be turned off with cache=false, and when colours look wrong — or a change of scheme stubbornly fails to show up — deleting the whole directory is the quickest cure. Keep it out of version control.
So which one should you use
There is really only one axis: where this document will be compiled. If it is only ever your own machine, or a curated environment like Overleaf, minted's colouring is plainly better — and with minted 3 on TeX Live 2025 or later you no longer pay the old price for it. If, on the other hand, you control neither your co-authors' machines nor the processing chain at the other end, the fact that listings simply works is worth more than accurate colours. When in doubt, work down this list.
- You cannot install external tools, or cannot use shell escape → listings, no contest.
\usepackage{listings}is the whole of it. - Highlighting accuracy and language coverage come first → minted. Pygments-based colouring is in a class of its own.
- Your code contains Japanese, Chinese or Korean → change engine rather than package. Typeset with XeLaTeX or LuaLaTeX and point
\setmonofontat a monospaced face that covers the script. - You want no colouring and no line numbers, just the text as typed →
verbatimorfancyvrbare lighter. - You want to write an algorithm as pseudocode rather than running code →
algorithm2eandalgpseudocodeare the purpose-built tools.