Most applications have at least one TextField. From login forms to sign-up forms and any other types of one-line user input. Each of them needs slightly different user text – numbers, email addresses and text-only fields are a common requirement.

There are so many different uses for a text field it can be frustrating that JavaFX only has one TextField type. But, by setting an appropriate TextFormatter the JavaFX TextField allows you to filter input on the way in, ensuring your user’s input is valid, and convert it on the way out so you get it out exactly as you need it.

In JavaFX, the TextField is a control that holds a single-line of unformatted, free text input. The character count of a TextField can be limited, or its contents validated by setting a TextFormatter, and the text can be bound as a property.

What you’ll get from this tutorial

  • Creating a text field, setting prompt text, alignment and size
  • Validating user input for number-only, text-only and email-format text fields (and more…)
  • Getting pre-formatted output as sensible objects like LocalDate and Double, rather than converting everything from strings!
  • Using CSS to give the user feedback about their input.

You’ll end up with a text field that validates user input, provides them with feedback, and hands you a nicely formatted object.

Creating a TextField

TextFields can be created either blank or with some initial text, which is passed into the constructor as a String.

TextField emptyTextField = new TextField();
TextField otherTextField = new TextField​("Initial Textfield Text");

Empty text fields can also display a subtle prompt text, which is present when the field is empty and disappears when user input is added to the field.

Adding Prompt Text

Prompt text really useful for telling a user what they’re supposed to be entering without having to embed that information in a separate label in the scene.

textField.setPromptText("Please enter a number");

Set the alignment

The alignment of a text field can be set both vertically and horizontally using a single method: setAlignment(Pos value).

Generally you won’t notice the vertical alignment of a TextField, because the height of the TextField is constrained to the correct height for a single line (plus padding) by default. That being said, there are a few situations where you might want to constrain the size of the text field, and in those cases in can be handy.

textField.setAlignment(Pos.CENTER)

Setting the size of a TextField

Text fields are a Control, and because of that, they adjust their size in a way that you might not expect. Like other controls, they’re designed to expand in ways that are suitable for that control, rather than in every direction, like regions and layouts.

Here’s are the default behaviours:

  • Text fields size themselves vertically to accommodate the text they contain, plus padding.
  • A TextField will not (by default) expand vertically to fill a layout region, even if you’re requesting it using a static method like VBox.vgrow().
  • Text fields expand horizontally to fill any space available to them

I’ll show you how to tweak each behaviour in turn.

Constraining a TextField’s height regardless of input font size

If you don’t want a TextField to size itself, you can constrain a text field, and prevent it from growing and shrinking depending on the size of the text it contains.

  • First, set the height you want absolutely using setMaxHeight()
  • Then, set the same height using setMinHeight()
  • Finally, set the preferred height to Region.USE_COMPUTED_SIZE
textField.setMaxHeight(200);
textField.setMinHeight(200);
textField.setPrefHeight(Region.USE_COMPUTED_SIZE);
Allowing a TextField to expand vertically

To allow a TextField to expand to fill a layout region, set the maximum width and height to Double.MAX_VALUE. This signals to JavaFX that the layout can be resized above its preferred height.

textField.setMaxHeight(Double.MAX_VALUE);
Prevent a TextField from expanding horizontally

To prevent a TextField from expanding horizontally, set the maximum value to which you’re willing to have it resized by it’s parent layout using setMaxWidth(). If you don’t want it to shrink either, also invoke setMinWidth().

textField.setMaxWidth(200);
textField.setMinWidth(200);

Validating user input

If you’ve gone to the trouble of setting prompt text asking the user to input something – an email, or a number – it seems a shame to let them just enter any old text. By using a TextFormatter, we can ensure that user input is validated before it’s set as the text value of the field.

TextFormatter class

If you were with JavaFX at the beginning, validating user input to any text input control was a nightmare riddled with buggy solutions based on property change listeners.

Since 2015 (version 8u40 for the curious), any text input controls like the TextField have come with a TextFormatter, which ensures that any changes to the text content can be tested, checked and changed where appropriate.

Checking User Input

To check whether a user’s input matches with our expectations, we’ll set a function called a UnaryOperator. I don’t know about you, but I panic when I see words like that..

A unary operator is a function that takes an argument of one type – say Double – and returns a value of the same type. That’s it!

What that means in this case, is we can assess the change made to the text field, assess whether the change is acceptable and modify it before passing it back to the TextField to be applied.

The change in a text field’s content is represented by the inner class Change, which we access as TextFormatter.Change.

We can define a unary operator to assess our change either as a new anonymous inner class, or using a lambda. In this example, we’ll check whether the new character is numeric. If it is, we’ll return the change unmodified, because it’s allowed.

Testing for numbers

To validate numeric inputs, we just need to assess the text of the change that’s currently being made. If it’s non-numeric text being entered, we’ll replace the change text with an empty string (essentially rejecting the change), and return the change to be implemented.

Here, the change is non-numeric, so it’s modified to reject the change before it’s applied to the text field.

I prefer lambdas because they’re more concise. As a lambda, it looks like:

UnaryOperator<TextFormatter.Change> numberValidationFormatter = change -> {
    if(change.getText().matches("\\d+")){
        return change; //if change is a number
    } else {
        change.setText(""); //else make no change
        change.setRange(    //don't remove any selected text either.
                change.getRangeStart(),
                change.getRangeStart()
        );
        return change;
    }
};

As a quick note on how this works, regular expressions are a life-saver when it comes to checking the format of certain inputs. In the example above, I checked for the numeric format by using the String built-in method matches() with the regular expression \d+.

If you’ve never used regular expressions, \d checks for numbers (the \ tells the matcher you’re not looking for the actual letter d) and + applies a counting rule that it will match one or more of the \d characters.

Regular expressions I use regularly

I’ve put together a few I use relatively regularly. They’re not always fool-proof, but they work in most settings.

PurposeRegular Expression
Text only (no numbers)[a-zA-Z]+
Number (integers only)\\d+
Number (doubles allowed)
e.g. 47.90
\\d+\\.\\d+
Number (Larger numbers with optional commas)
e.g. 1,000
\\d*|\\d+\\,\\d*
Dates (YYYY-MM-DD)((19|2[0-9])[0-9]{2})-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])
URL
(from urlregex.com)
^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]
Email addresses
(from emailregex.com)
(?:[a-z0-9!#$%&'+/=?^_{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_{|}~-]+)|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\[\x01-\x09\x0b\x0c\x0e-\x7f])")@(?:(?:a-z0-9?.)+a-z0-9?|[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-][a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\[\x01-\x09\x0b\x0c\x0e-\x7f])+)])

In some cases, you’ll want to test the match against the changed text, like when you’re checking the input is numeric. In other cases, you’ll want to check against the entire entered text using TextFormatter.Change method getControlNewText().

Now we have validated input, we can also use the TextFormatter to provide formatted output. This can be done either in combination with a UnaryOperator to validate the user’s input, or separately.

Getting formatted output

As well as validating the user’s input the TextFomatter allows you to collect formatted output from a TextField in the form of any object you’ve defined upfront.

All you have to do is provide a method to move between the text contained in the text field and the object you want.

This way, we can access a pre-converted object by invoking getValue() on the TextFormatter, rather than invoking getText() on the TextField. Of course, you an still get unconverted text through calling the text field’s getText() and getCharacters() methods.

We set the method by which to convert between the text field’s String content and our object using a StringConverter. The TextFormatter doesn’t have a converter by default, so if you invoke getValue() on a text formatter without a string converter specified, it will return null.

Defining a StringConverter

A StringConverter is designed to work two ways – a generic object T to a String, and from a String to the same T type. It has two methods:

public abstract String toString(T object);
public abstract T fromString(String string);

We have to overwrite both of them when we define a StringConverter, because they’re abstract.

Example StringConverter for date input

As an example, let’s assume we’ve asked our user to enter a date in the format YYYY-MM-DD. We want to set up the text field so that when we access the value, it’s already been converted to the right date format.

To create a StringConverter that will hand us a LocalDate directly from the TextFormatter, we define the fromString() to return our date:

StringConverter<LocalDate> dateStringConverter = new StringConverter<>() {
    @Override
    public String toString(LocalDate object) {
        return object.toString();
    }

    @Override
    public LocalDate fromString(String string) {
        return LocalDate.parse(string);
    }
};

To apply this to the text field’s TextFormatter, we set it at initialization. We also need specify a default value as LocalDate.now(). That’s needed because if the converter is unable to create a LocalDate object, it needs a default value to return instead.

TextFormatter<LocalDate> formatter = new TextFormatter<>(
        dateStringConverter,   //converter
        LocalDate.now()        //default value
);

When it comes to getting output from a TextField, this just seems to fit so much more neatly into the object oriented programming principle of encapsulated responsibility. Now, rather than having to convert the String to a LocalDate every time we use this TextField, we can query the formatter pbject directly for a LocalDate.

LocalDate date = formatter.getValue();

If for whatever reason, our user sneaks past our input validation, and the local date parser can’t create an object from the text field’s content, it will return the default value instead.

Giving user feedback

Occasionally, it’s beneficial to allow users to input incorrect text, but highlight to them that it’s going to cause problems down the line.

This might be useful if you’re asking a user to input an email address, which is relatively complex, and not particularly suited to validation as it’s being typed. In this case, I’d recommend you style the TextField to visually highlight the error.

There are a few ways to accomplish this, but I find the cleanest way is to add a Pseudoclass “error”.

This has the benefit of being reusable, because you can apply the Pseudoclass to many different controls, without having to create multiple error styles (“text-error”, “combo-error”). Nor do you have to create uber-styles where you try to style multiple components in a single block of CSS.

Pseudo-classes

A pseudo class represents the state of a component: some common pseudo classes are ‘focussed’, ‘hover’, and ‘armed’.

PseudoclassDescription
FocussedThe element has the focus of the scene, such as when a TextField is being edited.
HoverThe state of a component when a mouse is hovering over it.
ArmedA button has been clicked, but not released, so releasing the mouse without moving it will trigger the button action

More than one pseudo class can be applied at once, making them perfect for reflecting a temporary state, such as when the text input is incorrect.

Creating a PseudoClass

To create a PseudoClass object, we invoke the PseudoClass static utility method getPseudoClass(), which takes the name of the pseudo class as an argument.

PseudoClass errorClass = PseudoClass.getPseudoClass("error");

To apply a pseudo class, we invoke pseudoClassStateChanged() on the object for which we want to apply the change. This takes two arguments – the PseudoClass object itself, and a boolean variable that describes whether we should apply, or disapply the state change.

That might seem slightly weird, when other styles are added using getStyleClasses().add(), but this design choice allows us to apply a test and return the result directly as the argument.

In this case, we want to apply an ‘error’ pseudoclass only when the input text is incorrect. This time we can safely do this by listening to the value of the text field, because we’re not changing state while we listen to it.

textField.textProperty().addListener(event -> {
    System.out.println("Changed");
    textField.pseudoClassStateChanged(
            PseudoClass.getPseudoClass("error"),
            !textField.getText().isEmpty() &&
                    !textField.getText().matches("(?:[a-z0-9!#$%&'*+=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])")
    );
});

In this case, we just need to make sure that the text field is subscribed to the stylesheet in the usual way (this can be defined in code, or in FXML), and then define our error pseudo class in the stylesheet.

.text-input:error {
    -fx-text-fill: white;
    -fx-border-insets: 0;
    -fx-border-width: 2px;
    -fx-border-color: red;
    -fx-border-style: hidden hidden solid hidden;
    -fx-background-color: derive(red, 99%);
}

Conclusions

Text fields are a versatile control, which support extensive user input validation, as well as custom value conversion. This ensures users input valid text, and that user classes aren’t burdened with string conversion when they come to access the value.

As a control, their default sizing behaviour is frequently suitable. However, their sizing behaviour can be tweaked to allow optimal performance in the layout you’re using.

Finally, CSS styling is really useful for creating text fields with robust user feedback, indicating when an error has been made and prompting the user to reconsider the text they’ve entered. Pseudo classes are a perfect way to achieve this.