Jansi is a Java library for generating and processing ANSI escape sequences. These sequences let a terminal display colors and styles, move the cursor, erase parts of the screen, and perform other terminal operations.
For example, the following bytes mean “use bold red text,” print FAILED, and
then restore the terminal’s default attributes:
\u001B[1;31mFAILED\u001B[0mWriting those codes by hand is difficult to read and easy to get wrong. Jansi provides a fluent Java API for constructing them and terminal-aware streams that can pass through, adapt, or remove them as appropriate.
Jansi is useful for command-line applications, installers, build tools, and small terminal dashboards. It is not a logging framework and is not a complete interactive-shell framework. Use JLine’s terminal and line-reader APIs when an application also needs command history, completion, input editing, signal handling, or a rich full-screen interface.
The Project Moved to JLine #
Many examples on the internet use these old Maven coordinates and imports:
org.fusesource.jansi:jansi
org.fusesource.jansi.Ansi
org.fusesource.jansi.AnsiConsoleThe original FuseSource project is no longer maintained. Jansi was moved into the JLine project, and new applications should use:
org.jline:jansi
org.jline.jansi.Ansi
org.jline.jansi.AnsiConsoleThe API remains familiar, but the package change matters when copying examples or integrating with a library that discovers Jansi by class name.
Add the maintained JLine artifact to Maven:
<properties>
<jline.version>4.0.0</jline.version>
</properties>
<dependencies>
<dependency>
<groupId>org.jline</groupId>
<artifactId>jansi</artifactId>
<version>${jline.version}</version>
</dependency>
</dependencies>Or with Gradle:
dependencies {
implementation "org.jline:jansi:4.0.0"
}Use the version managed by the application’s JLine bill of materials or dependency management when JLine is already present. This avoids loading incompatible versions of closely related JLine modules.
ANSI Escape Sequences #
An ANSI control sequence starts with the escape character, represented in Java
as \u001B or \033, followed by instructions interpreted by the terminal.
The Select Graphic Rendition, or SGR, family controls color and style:
String red = "\u001B[31m";
String bold = "\u001B[1m";
String reset = "\u001B[0m";
System.out.println(red + bold + "Danger" + reset);This works in a compatible terminal, but direct string concatenation has several problems:
- numeric control codes hide the intent;
- forgetting the reset can color every later line;
- terminals support different color depths and capabilities;
- escape sequences are undesirable in redirected text, files, and machine- readable output;
- cursor operations are stateful and can corrupt output when several threads write concurrently.
Jansi addresses the first four problems. Coordinating concurrent writers is still the application’s responsibility.
Building Styled Text #
The org.jline.jansi.Ansi builder appends text and control sequences to one
object:
import org.jline.jansi.Ansi;
import static org.jline.jansi.Ansi.Color.GREEN;
import static org.jline.jansi.Ansi.Color.RED;
public final class StatusPrinter {
private StatusPrinter() {
}
public static void printResult(boolean successful) {
Ansi line = Ansi.ansi()
.bold()
.fg(successful ? GREEN : RED)
.a(successful ? "SUCCESS" : "FAILED")
.reset()
.a(" Deployment finished");
System.out.println(line);
}
}Calling toString() is implicit when the Ansi object is passed to
println. The builder can be converted explicitly when an API requires a
String:
String message = Ansi.ansi()
.fgBright(Ansi.Color.CYAN)
.a("Listening on port 8080")
.reset()
.toString();The commonly used methods include:
| Method | Effect |
|---|---|
fg(color) | Sets a foreground color |
fgBright(color) | Sets a bright foreground color |
bg(color) | Sets a background color |
bgBright(color) | Sets a bright background color |
bold() | Enables bold or increased intensity |
underline() | Enables underlining |
a(value) | Appends text or another value |
newline() | Appends the platform line separator |
reset() | Restores default colors and attributes |
The exact appearance is controlled by the terminal and its theme. “Bright black,” for example, is commonly rendered as gray, while bold may be displayed as brighter text rather than a heavier font.
Always reset attributes at the end of independently printed styled content:
System.out.println(
Ansi.ansi()
.fg(Ansi.Color.YELLOW)
.a("Warning:")
.reset()
.a(" cache is almost full")
);Resetting before the unstyled suffix also makes the intended boundary clear.
Backgrounds and Nested Styles #
Foreground, background, and text attributes can be combined:
String banner = Ansi.ansi()
.fg(Ansi.Color.WHITE)
.bg(Ansi.Color.RED)
.bold()
.a(" STOP ")
.reset()
.toString();reset() resets all attributes; it does not restore an earlier nested style.
When composing reusable fragments, either let one component own the entire
styled region or explicitly reapply the outer style after a reset. Do not
assume that terminal formatting behaves like nested HTML elements.
Renderer Syntax #
Jansi also supports a compact markup syntax:
String result = Ansi.ansi()
.render("@|bold,green Build passed|@")
.toString();The fluent API is usually clearer for conditional or composed output. Renderer
syntax is convenient for short, fixed messages, but should not be applied
blindly to untrusted text because @|...|@ has meaning to the renderer.
Installing Terminal-Aware Streams #
Generating an ANSI string and making it suitable for the current output
destination are separate concerns. Ansi generates sequences.
AnsiConsole supplies streams that process them.
The most direct application-wide setup replaces System.out and System.err:
import org.jline.jansi.Ansi;
import org.jline.jansi.AnsiConsole;
public final class CliApplication {
private CliApplication() {
}
public static void main(String[] args) {
AnsiConsole.systemInstall();
try {
System.out.println(
Ansi.ansi()
.fg(Ansi.Color.GREEN)
.a("Ready")
.reset()
);
} finally {
AnsiConsole.systemUninstall();
}
}
}Installation is reference-counted. Every call to systemInstall() must have a
corresponding call to systemUninstall(). The finally block is therefore
important even when the program normally exits immediately.
Replacing the global streams affects the entire process, including libraries
and logging systems that write to System.out or System.err. A library
should generally not do this on behalf of its host application. An application
that wants a smaller scope can write to Jansi’s streams directly:
AnsiConsole.out().println(
Ansi.ansi()
.fg(Ansi.Color.MAGENTA)
.a("Interactive mode")
.reset()
);AnsiConsole.out() and AnsiConsole.err() initialize and return
terminal-aware streams without assigning them to Java’s global stream fields.
Terminal Detection and Redirection #
Color is appropriate for a person looking at a terminal, but usually not for a file, pipe, test capture, log collector, or JSON consumer:
java -jar tool.jar > result.txt
java -jar tool.jar | jq .In its default mode, Jansi uses the terminal type and output destination to select its behavior:
- on a native terminal, ANSI sequences are retained and adapted to supported capabilities;
- on unsupported or redirected output, ANSI sequences are stripped;
- color depth can be inferred from terminal information such as
TERMandCOLORTERM.
The behavior can be overridden with Java system properties:
# Always remove ANSI sequences.
java -Djansi.mode=strip -jar tool.jar
# Emit ANSI even when output is redirected.
java -Djansi.mode=force -jar tool.jar
# Force a particular color depth.
java -Djansi.colors=256 -jar tool.jar
java -Djansi.colors=truecolor -jar tool.jarjansi.out.mode and jansi.err.mode override the mode for one stream, while
jansi.out.colors and jansi.err.colors do the same for color depth.
Forcing color is useful when another program understands ANSI sequences. Stripping is appropriate for deterministic plain-text output. Defaults are normally best for an end-user CLI.
Do not use System.console() != null as the only color test. It is a useful
hint, but IDE terminals, pseudo-terminals, containers, pipes, and test runners
make terminal detection more nuanced. Let the terminal library make the
decision and provide an explicit --color=auto|always|never option for users
when predictable control matters.
Windows Support #
Historically, Jansi was especially useful because older Windows consoles did not interpret ANSI sequences. Jansi could translate terminal operations using platform-specific support.
Modern Windows 10 and later terminals generally support ANSI natively,
including Windows Terminal and current cmd.exe and PowerShell environments.
That means AnsiConsole.systemInstall() is no longer required merely to make
basic colors work on most current systems. It remains useful when an
application wants Jansi’s consistent stream processing, redirected-output
handling, color-depth adaptation, or support for a wider deployment matrix.
Test the actual environments supported by the application. “Windows,” an IDE console, an SSH session, and a CI runner do not describe one uniform set of terminal capabilities.
Cursor and Screen Control #
Jansi can generate more than color codes:
Ansi progress = Ansi.ansi()
.cursorUp(1)
.eraseLine()
.a("Progress: 75%");
System.out.print(progress);
System.out.flush();Other operations include:
- positioning the cursor with
cursor(row, column); - moving it up, down, left, or right;
- saving and restoring cursor position;
- erasing a line or the screen;
- scrolling terminal content.
These operations should be reserved for interactive terminal output. If the same bytes are written to a file or mixed with ordinary logs, the result is unreadable. Cursor-based displays should also have one owner that serializes updates. Background log messages can otherwise move the cursor between an erase and a redraw.
For a sophisticated interactive application, use JLine’s Terminal,
LineReader, and display abstractions rather than building a terminal user
interface entirely from cursor sequences.
Integrating with SLF4J and Logback #
SLF4J, Logback, and Jansi solve different problems:
| Component | Responsibility |
|---|---|
| SLF4J | API through which application code creates logging events |
| Logback | SLF4J provider that filters, formats, and routes those events |
| ANSI pattern converters | Insert style sequences into formatted console text |
| Jansi | Generate or process ANSI sequences for a terminal stream |
SLF4J itself does not color output and does not know whether an event is going to a terminal, a rolling file, JSON, or a remote collector. Application code should therefore log semantic, uncolored values:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class ImportService {
private static final Logger LOGGER =
LoggerFactory.getLogger(ImportService.class);
public void importFile(String name, int recordCount) {
LOGGER.info("Imported {} records from {}", recordCount, name);
}
}Do not put Ansi.ansi() values into general log messages:
// Avoid this: every appender receives control characters in the message.
LOGGER.info("{} Import failed", Ansi.ansi().fg(Ansi.Color.RED).a("ERROR").reset());The same logging event may be sent to both a console and a file. Styling in the message contaminates every destination and makes search, parsing, alerting, and tests harder.
A Colored Console and a Plain File #
Logback’s pattern converters can add ANSI only while formatting the console
appender. %highlight chooses a color according to the log level. Converters
such as %red, %yellow, %green, %blue, %magenta, %cyan, %white,
and %boldRed apply a fixed style.
<configuration>
<appender name="CONSOLE"
class="ch.qos.logback.core.ConsoleAppender">
<target>System.err</target>
<encoder>
<pattern>
%d{HH:mm:ss.SSS} %highlight(%-5level)
%cyan(%logger{36}) - %msg%n
</pattern>
</encoder>
</appender>
<appender name="FILE"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/application.log</file>
<rollingPolicy
class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>
logs/application.%d{yyyy-MM-dd}.log
</fileNamePattern>
<maxHistory>14</maxHistory>
</rollingPolicy>
<encoder>
<pattern>
%d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX} %-5level
%logger{36} - %msg%n
</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
</root>
</configuration>The console pattern contains the color converters. The file pattern does not. The logging calls remain independent of presentation.
In a modern terminal, the ANSI sequences emitted by Logback usually work
without Jansi. If the application globally installs the maintained Jansi
stream before Logback’s console appender captures System.err, Jansi can
process those sequences:
public static void main(String[] args) {
AnsiConsole.systemInstall();
try {
runApplication(args);
} finally {
AnsiConsole.systemUninstall();
}
}Initialize the terminal stream before the logging system creates its console appender. Otherwise, the appender may retain a reference to the original stream.
The withJansi Compatibility Trap
#
Logback’s ConsoleAppender has a <withJansi>true</withJansi> option. Current
Logback source still discovers this legacy class by name:
org.fusesource.jansi.AnsiConsoleThe maintained JLine artifact exposes:
org.jline.jansi.AnsiConsoleConsequently, <withJansi>true</withJansi> does not integrate with the
relocated org.jline:jansi artifact. Adding the unmaintained
org.fusesource.jansi:jansi artifact merely to satisfy that reflective lookup
is not a good default for a new application.
Prefer one of these approaches:
- let a modern terminal render Logback’s ANSI converters directly;
- install the maintained
org.jline.jansi.AnsiConsolestreams in application bootstrap code when Jansi processing is required; - avoid ANSI converters when output is destined for a collector or a format such as JSON.
Jansi does not decide log levels, format events, rotate files, or route logs. Those remain Logback responsibilities.
Spring Boot Logging #
Spring Boot already has its own ANSI support for Logback and Log4j2
configuration. Its %clr conversion word can color content by level or by an
explicit style:
<pattern>
%clr(%d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX}){faint}
%clr(%-5level)
%clr(${PID:- }){magenta}
%clr([%thread]){faint}
%clr(%logger{39}){cyan} : %msg%n
</pattern>Boot controls ANSI emission through:
spring.output.ansi.enabled=detectThe supported policies are detect, always, and never. detect is the
normal choice for applications used both interactively and through redirected
output.
Spring Boot’s %clr converter and ANSI policy are separate from direct Jansi
usage. A typical Boot service should use %clr for console logs and keep file
or JSON appenders plain. Add Jansi only if the application also prints its own
terminal UI or requires Jansi-specific stream processing.
Separating Human and Machine Output #
A reliable CLI treats its output channels as an interface:
- use
stdoutfor the command’s primary result; - use
stderrfor diagnostics, progress, and logs; - keep structured output modes free of ANSI sequences;
- disable cursor movement when output is not interactive;
- provide an explicit color policy when scripts need stable behavior.
For example, a command that supports JSON should not decorate the JSON:
if (jsonOutput) {
System.out.println(json);
} else {
AnsiConsole.out().println(
Ansi.ansi()
.fg(Ansi.Color.GREEN)
.a("Created ")
.reset()
.a(resourceId)
);
}Color should improve human readability without changing the meaning or parseability of the underlying information. Never make color the only way to distinguish success from failure; also use words, symbols, exit codes, or structured fields.
Testing Terminal Output #
Most unit tests should verify the semantic text rather than depend on a real terminal. Put presentation behind a small formatter and make the color policy explicit:
import org.jline.jansi.Ansi;
public final class ResultFormatter {
public String success(String message, boolean colorEnabled) {
if (!colorEnabled) {
return "SUCCESS " + message;
}
return Ansi.ansi()
.fg(Ansi.Color.GREEN)
.bold()
.a("SUCCESS")
.reset()
.a(" ")
.a(message)
.toString();
}
}Then test both contracts:
assertEquals(
"SUCCESS uploaded",
formatter.success("uploaded", false)
);
assertTrue(
formatter.success("uploaded", true).contains("\u001B[")
);Exact ANSI strings can be asserted when the sequence itself is the contract,
but such tests are tightly coupled to formatting choices. Integration tests for
auto-detection should run in both a pseudo-terminal and a redirected process;
capturing System.out in a byte array is not equivalent to attaching a
terminal.
Avoid changing Jansi’s global enabled state or replacing System.out in tests
that run concurrently. Prefer injected streams and local formatters where
possible, and restore global state in finally when a process-level test
really needs it.
Practical Guidelines #
Use Jansi deliberately:
- depend on the maintained
org.jline:jansiartifact, not the old FuseSource coordinates; - use the
Ansibuilder instead of handwritten escape codes; - end independent styled regions with
reset(); - let terminal-aware code strip color from redirected output by default;
- keep ANSI out of files, JSON, and general SLF4J message arguments;
- add color in the Logback console pattern, not in application logging calls;
- do not expect Logback’s legacy
<withJansi>option to find the relocated JLine package; - install and uninstall global Jansi streams symmetrically;
- serialize cursor-based output and keep it separate from ordinary logs;
- offer users
auto,always, andnevercolor policies when output is a public CLI contract.
With those boundaries, terminal output can be expressive for people while remaining predictable for files, pipelines, tests, and logging systems.
Comments
Read or comment on GitHub (sign-in required to comment)