Interactive Console Applications in Java
Learn how to make your applications pay more attention to what users are telling them.
Join the DZone community and get the full member experience.
Join For FreeConsole applications come in several different forms:
- Applications having a text user interface that provides a GUI-like experience by using text-based components such as windows, dialogs, menus, text fields and buttons.
Examples include the file manager Midnight Commander, the Linux tool menuconfig, or the Lynx browser.
For developing such applications, you can use one of the following Java libraries: Lanterna, Charva, or Java Curses.
- Programs that take all input as command-line arguments. Most commands in Unix-like operating systems belong to this category.
Many Java libraries for parsing command-line arguments exist: Apache Commons-CLI, JOpt Simple, args4j, JCommander, Jewel CLI, and many other.
- Command-line interface shells, where the user issues commands to the program in the form of successive lines of text.
Examples include Unix shells such as bash, or REPL environments such as JShell, which is planned to be included in Java 9.
The Java libraries Spring Shell and Cliche help creating this kind of applications.
Interactive console applications that prompt the user to provide information. Instead of issuing commands as in the case of shell applications, the user controls the program in a more limited way, by specifying some parameter values or choosing an option from a list.
The last type of console applications in the above list is the typical choice when starting to learn a new programming language. However, people tend to switch to one of the other types when implementing "serious" console applications. If the number of input parameters is relatively small, programs taking the input as command-line arguments are preferred, in order to allow pipelining. Otherwise, a shell interface or a GUI-like text interface are usually more appropriate.
So, are interactive console applications that prompt for user input still relevant today, or are they on the brink of extinction? I assume that many such applications are still written, but they usually don't end up being widely known, because they are mostly created for personal use or as prototypes.
If you decide to write such an application, you have to read user input in an interactive manner. What is the best way to do this in Java? Let's look at the most frequently used approaches:
- Wrapping the standard input stream in a BufferedReader and reading user input line by line:
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String line = reader.readLine();
It is your responsibility to parse the input.
- Using a java.util.Scanner, which provides methods for parsing primitive types and strings using regular expressions:
Scanner scanner = new Scanner(System.in);
String productId = scanner.next("[A-Z]{3}-[0-9]{5}");
int quantity = scanner.nextInt();
float price = scanner.nextFloat();
- Using a java.io.Console, which allows you to read not only ordinary text, but also passwords, by suppressing echoing:
Console console = System.console();
String user = console.readLine("user: ");
String password = new String(console.readPassword("password: "));
Console does not provide methods for parsing the input.
All of the above methods have drawbacks. An ideal solution would allow both parsing the input and reading passwords. If your application needs to read sensitive data, you are forced to use a java.io.Console, because there is no easy way to suppress echoing for the standard input stream. Aside of the annoying fact that you have to parse yourself the input, there is another problem with this solution: your virtual machine may not have a Console. This happens if the virtual machine has not been started from an interactive command line or if the standard input and output streams have been redirected. When running your application in an IDE, System.console()
usually returns null.
Console is a final class with a private constructor. A virtual machine has at most one instance of this class, which can be obtained by invoking System.console()
. You cannot provide your own Console implementation. What you can do instead, is to introduce an abstraction layer that provides the same functionality. By writing code that interacts only with this abstraction layer and not directly with a Console instance, you can handle the case of System.console()
returning null.
This is the approach I took to develop Text-IO, a library for creating interactive console applications. The abstraction layer, which in Text-IO is represented by the TextTerminal interface, is extremely simple: a text terminal is only required to provide methods for printing a line of text and for reading a line of text, optionally masking the input. By default, the Text-IO library tries to use a text terminal backed by the java.io.Console. If the virtual machine has no console device, a Swing-based terminal is used instead. (The mechanism for choosing a text terminal is actually more complex and provides a Service Provider Interface for configuring additional text terminals.)
Some other useful features of the Text-IO library include:
- Support for reading values with various data types.
- The ability to specify default values.
- The ability to specify constraints on the input values (format patterns, value ranges, length constraints etc.).
- The ability to select one or more values from a list.
- The ability to customize the error messages.
The code fragment below illustrates the use of Text-IO:
TextIO textIO = TextIoFactory.getTextIO();
String user = textIO.newStringInputReader()
.withDefaultValue("admin")
.read("Username");
String password = textIO.newStringInputReader()
.withMinLength(6)
.withInputMasking(true)
.read("Password");
int age = textIO.newIntInputReader()
.withMinVal(13)
.read("Age");
Month month = textIO.newEnumInputReader(Month.class)
.read("What month were you born in?");
textIO.getTextTerminal().printf("User %s is %d years old, was born in %s and has the password %s.\n",
user, age, month, password);
An example session using the above code produces the following output:
Username [admin]:
Password: ****
Invalid value.
Expected a string with at least 6 characters.
Password: ******
Age: 9
Invalid value.
Expected an integer value greater than or equal to 13.
Age: 19
What month were you born in?
1: JANUARY
2: FEBRUARY
3: MARCH
4: APRIL
5: MAY
6: JUNE
7: JULY
8: AUGUST
9: SEPTEMBER
10: OCTOBER
11: NOVEMBER
12: DECEMBER
Enter your choice: 5
User admin is 19 years old, was born in MAY and has the password 123456.
Click here to see an animated image of this example session in a Swing-based console.
A further advantage of using Text-IO is that you can easily test your code by implementing a mock text terminal that provides the desired input for your test case.
Opinions expressed by DZone contributors are their own.
Comments