46

Is there a way to find the width of the console in which my Java program is running?

I would like this to be cross platform if possible...

I have no desire to change the width of the buffer or the window, I just want to know its width so I can properly format text that is being printed to screen.

masher
  • 3,814
  • 4
  • 31
  • 35
  • Not an answer, but line wrapping was a problem for me as I wanted to move the cursor back and forth and line wrapping was an issue for this. This could have been solved by working out the console width, but this was difficult so instead my solution on Mac at least (and probably works on Linux too) is `tput rmam` to disable line wrapping and `tput smam` enable line wrapping (https://itectec.com/askdifferent/macos-disable-line-wrapping-for-output-in-the-terminal/) Can either be executed directly in the console, or executed as a bash command straight from Java. –  Dec 28 '21 at 10:48

8 Answers8

31

There are no reliable cross-platform solutions to this problem. Indeed, there are situations where it is not possible to know what the real console width is.

(See other answers for approaches that work some of the time and/or on some platforms. But beware of the limitations ...)

For example, on a Linux system you can typically find out the notional terminal dimensions from the LINES and COLUMNS environment variables. While these variables are automatically updated when you resize some "terminal emulator" windows, this is not always the case. Indeed, in the case of a remote console connected via telnet protocol, there is no way to get the actual terminal dimensions to the user's shell.

EDIT: Just to add that if the user changes the dimensions of his/her xterm on Linux after launching a Java app, the Java app won't be notified, and it won't see the new dimensions reflected in its copy of the LINES and COLUMNS environment variables!

EDIT 2: My mistake: LINES and COLUMNS are bash shell variables, and they are not exported to the environment by default. You can "fix" this by running export COLUMNS LINES before you run your Java application.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
  • 1
    I was hoping there'd be something that would return the width/height etc or -1 if it can't be determined... oh well. Welcome hard-coded constants! – masher Aug 17 '09 at 23:22
  • @masher: the problem is that on Linux, the answer would nearly always have to be "cannot be determined with any certainty". – Stephen C Aug 17 '09 at 23:29
  • I tried to do this with System.getenv("COLUMNS") but it returned null. Ubuntu 10.04 / GNOME 2.30.2 – jsj May 04 '11 at 04:25
  • 3
    I use a script to invoke my Java app, and the script does an `eval \`resize\`; export COLUMNS` before invoking the Java under Linux. – Ogre Psalm33 Sep 20 '11 at 18:54
  • @Ogre Psalm33 - that works to a point. But if the user manually resizes the console window after launching the Java app, the app will still use the old size. That's one of the aspects of "can't be determined with certainty" ... – Stephen C Sep 21 '11 at 01:36
  • I've found a library that I've confirmed works under Linux and Windows cmd. Please see my detailed reply below. – Michael Scheper Oct 22 '13 at 02:30
  • @MichaelScheper - According to what you say in your answer, *"works some of the time"* would be a better characterization. – Stephen C Oct 01 '15 at 22:31
  • @StephenC: I'd say _most_ of the time, actually, and in pretty well defined situations. – Michael Scheper Oct 07 '15 at 00:34
  • You are entitled to your opinion :-) – Stephen C Oct 07 '15 at 02:03
  • @StephenC: Now that I'm revisiting this: Have you found cases in Linux or in the Windows cmd where it doesn't work reliably? I don't expect it to keep up when the user resizes the window; a lot of GNU programs don't, either. But I'd like to know if there's some new Windows quirk that users may bug me about some day. – Michael Scheper Mar 27 '17 at 18:38
  • Well, as noted previously, it doesn't work in all circumstances when a Linux terminal window is resized. That is what I was talking about. Like I said, your solution only works some of the time. I'm not an expert on Windows behavior so I can't comment on that aspect. (And it looks like you have found other edge cases too.) IMO, a totally reliable, cross-platform solution is a unicorn. The best you will find is a horse with a broomstick strapped to its forehead. – Stephen C Jul 09 '18 at 04:59
  • While it is true that there is no reliable way to get the amount of columns and lines of the console, there should be more reliable ways as the ones described here. As you have noticed, the variables `LINES` and `COLUMNS` described above are provided by *bash*. But *bash* gets these information from the environment. In theory, you can get these information from the environment just like *bash* is doing it. (Many applications like *apt* on Ubuntu are doing it.) The question is whether there is an Java API to do so. – JojOatXGME Oct 28 '20 at 15:42
  • @JojOatXGME - If you dig hard enough, you will find that there are scenarios where `bash` and `apt` don't get the console dimensions right. – Stephen C Aug 27 '21 at 11:32
25

Actually, a Java library already exists to do this in Java: JLine 2. (There's an old version on SourceForce, but that link to GitHub seems to be the latest.)

This worked for me under Linux (Mint 14) and Windows (I don't know what version), using JLine 2.11:

terminalWidth = jline.TerminalFactory.get().getWidth();

JLine promises to work on Mac, too.

I've found that it returns bad widths (like 1!) under the Eclipse console (but even Java's native Console doesn't work under Eclipse!), and, unfortunately, under Cygwin. But I've worked around this with code that checks for unreasonable values (< 10) and just uses 80 in those cases.

Update for JLine 3 (per Mark—thanks, mate!):

terminalWidth = org.jline.terminal.TerminalBuilder.terminal().getWidth()
Michael Scheper
  • 6,514
  • 7
  • 63
  • 76
  • 6
    On Unix, JLine simply calls `stty -a` in [`TerminalLineSetting`](https://github.com/jline/jline2/blob/master/src/main/java/jline/internal/TerminalLineSettings.java), on Windows it hooks into a closed-source part of the JAnsi library via `org.fusesource.jansi.internal.WindowsSupport`. – dimo414 Feb 15 '16 at 13:33
  • 1
    For JLine3, it would be "org.jline.terminal.TerminalBuilder.terminal().getWidth()" – Mark Jun 29 '18 at 19:47
  • It appears that JLine3 can be configured to use [Jansi](https://fusesource.github.io/jansi/), so if I already have Jansi installed, is there a way I can determine the console with with just Jansi without needing the overhead of a JLine `Terminal` and such? – Garret Wilson Jul 13 '22 at 14:58
14

There's a trick that you can use based on ANSI Escape Codes. They don't provide a direct way to query the console size, but they do have a command for requesting the current cursor position. By moving the cursor to a really high row and column and then requesting the cursor position you can get an accurate measurement.

Combine this with commands to store/restore the cursor position, as in the following example:

Send the following sequences to the terminal (stdout)

"\u001b[s"             // save cursor position
"\u001b[5000;5000H"    // move to col 5000 row 5000
"\u001b[6n"            // request cursor position
"\u001b[u"             // restore cursor position

Now watch stdin, you should receive a sequece that looks like \u001b[25;80R", where 25 is the row count, and 80 the columns.

I first saw this used in the Lanterna library.

Update:

There are really four different ways that I know of to achieve this, but they all make certain assumptions about the environment the program is running in, or the terminal device/emulator it is talking to.

  • Using the VT100 protocol. This is what this solution does, it assumes you are talking over stdin/stdout to a terminal emulator that honors these escape codes. This seems like a relatively safe assumption for a CLI program, but e.g. if someone is using cmd.exe this likely won't work.
  • terminfo/termcap. These are databases with terminal information, which you can query for instance with tput. Operating system dependent, and assumes you are connected to a TTY device. Won't work over ssh for instance.
  • Using the telnet protocol. Telnet has its own affordances for querying the screen size, but of course this only works if people connect to your application via the telnet client, not really an option in most cases.
  • Rely on the shell (e.g. bash), this is what solutions that use COLUMNS/ROWS variables do. Far from universal, but could work quite well if you provide a wrapper script for your app that makes sure the necessary env vars are exported.
Arne Brasseur
  • 1,468
  • 13
  • 18
  • 1
    This might work when your application's `stdin` and `stdout` is connected to a terminal. However, it assumes that it always is. Users might pipe the output into a file which would break that assumption. In such case, the escape code would just be written to a file. The application should check if it is connected to a terminal before using this technique. Unfortunately, I suspect that there is no standard Java API to check that. In C, it might be something like [isatty](https://man7.org/linux/man-pages/man3/isatty.3.html). – JojOatXGME Oct 28 '20 at 15:55
  • The problem with this approach ist that many terminals work in canonical mode providing line based input. So this solution requires the user to press enter to enable java to read from `System.in` unless you find a way to set the ICANON flag. – Björn Zurmaar Aug 27 '21 at 10:59
11

Edit: See @dave_thompson_085's comment about ProcessBuilder, as that's almost certainly a better approach.

Another answer mentioned running tput cols in a script before you start your command. But if you want to run that after Java has already started, using Runtime.getRuntime().exec(), you'll find that tput can't talk to your terminal, because Java has redirected stdout and stderr. As a not-at-all-portable workaround, you can use the magical /dev/tty device, which refers to the terminal of the current process. That lets you run something like this:

Process p = Runtime.getRuntime().exec(new String[] {
    "bash", "-c", "tput cols 2> /dev/tty" });
// Read the output of this process to get your terminal width

This works for me on Linux, but I wouldn't expect it to work everywhere. It will hopefully work on Mac. It definitely won't work on Windows, though it might with Cygwin.

Jack O'Connor
  • 10,068
  • 4
  • 48
  • 53
  • 2
    `ProcessBuilder` since Java7 (2011) can pass Java's own stdin/out/err to a child process instead of the default redirected pipes; see `inheritIO()` and `redirect*(Redirect.INHERIT)` -- which doesn't help if `java` itself has been redirected by the user, but in that case why do you care about the width of an I/O device you're not using? – dave_thompson_085 Sep 11 '17 at 00:24
  • Sadly, this technique seems to be incompatible with IntelliJ's default terminal. I've seen others complain Eclipse has the same limitation. Any insight as to how to handle this for the terminals and the IDEs? Often we're writing/debugging with one but supporting the other. – tresf Sep 11 '20 at 18:16
4

Java 6 has a class java.io.Console, but it unfortunately lacks the functionality you're asking for. Getting the console window width is not possible with the standard Java library and pure, cross-platform Java.

Here is an alternative Java console library which allows you to get the screen size, but it includes a Windows-specific DLL. You might be able to take the source code and compile the C part into a Linux or Mac OS X shared library, so that it will work on those platforms as well.

Jesper
  • 202,709
  • 46
  • 318
  • 350
  • I found the Console API and was excited; and then I wasn't... For what I want to do, learning C and how to cross-compile is sort of like hammering a nail with a wrecking ball. – masher Aug 17 '09 at 23:20
  • @Jesper - it isn't really Java's fault. There are lots of things that make it impossible to get the current console size in all cases on a Linux / Unix machine. – Stephen C Sep 21 '11 at 01:38
3

I have been working on this problem before. I use a couple of different techniques. However it is difficult to have a truly cross platform solution.

I tried doing try something like this:

String os = System.getProperty("os.name").toLowerCase();

    //Windows
    if(os.contains("win")){
        System.out.append("Windows Detected");
        //set Windows Dos Terminal width 80, height 25
        Process p = Runtime.getRuntime().exec("mode 80, 25");
    }
    //Mac
    if(os.contains("mac")){
        System.out.println("Macintosh Detected");
        //... I dont know...try Google
    }
    //Linux
    if(os.contains("linux")){
        System.out.println("Linux Detected");

You can read/test and append "export COLUMNS" to the .bashrc file in every Linux users home directory with the String.contains("export COLUMNS") method and the user.dir property.

That would allow you to get the columns to load up every time the java app starts up.

Then I would pass it to a temp file. Like this:

try {
    ProcessBuilder pb = new ProcessBuilder("bash","-c","echo $COLUMNS >/home/$USER/bin/temp.txt" );  
        pb.start();

    }catch (Exception e) {
        System.out.println("exception happened - here's what I know: ");
        e.printStackTrace();
        System.exit(-1);
    }
}

Another option you have is to execute yor Java.jar with a bash script at startup. Inside the script you can use "tput cols" to get the width. Then pass that value to your Java app as a String[] arg.

Like so:

//#!/bin/bash

//#clear the screen

clear 

//#get the console width and height

c=$[$(tput cols)]

l=$[$(tput lines)]

//#pass the columns, lines and an optional third value as String[] args.

java -jar ~/bin/Plus.jar $c $l $1

why is this such a difficult task with Java? Obviously a good place to write a good API. I guess we could try Apache.commons.exec as well?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
aubreybourke
  • 439
  • 1
  • 5
  • 17
  • 2
    If `export COLUMNS` was called before the Java process started, you can simply do `System.getenv("COLUMNS")`; there's no need to launch a subprocess with `ProcessBuilder`. – dimo414 Feb 15 '16 at 12:59
1

For me, the only way to get an idea of the terminal window (still not correct when the window resizes) is to use a command like

ProcessBuilder pb = new ProcessBuilder("cmd.exe", "/c", "mode con");
pb.redirectError(ProcessBuilder.Redirect.INHERIT);

When run without the cmd.exe part, it shows that the command could not be found. Also note the redirectError part. If not used then the Java output size will be used, not the actual one. Only with this combination it was possible to grab the actual size.

Panayotis
  • 1,792
  • 23
  • 32
0

Python seems to have a good solution: 11.9.3. Querying the size of the output terminal. I wouldn't hold my breath waiting for this to be available in core Java, but you might be able to use Jython to make the Python functionality available.

Michael Scheper
  • 6,514
  • 7
  • 63
  • 76
  • 1
    As the `shutil` docs mention, this falls back to [`os.get_terminal_size()`](https://docs.python.org/3.5/library/os.html#os.get_terminal_size), which is OS-specific and [implemented](https://hg.python.org/cpython/file/3.5/Modules/posixmodule.c#l11128) in C. Using Jython might work for some use cases, but it's a rather heavy hammer. – dimo414 Feb 15 '16 at 13:16
  • It does not seem to be a "good solution" at all. – snorbi Feb 27 '23 at 19:15