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
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:
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.
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
.
The Change
class represents any state change in an TextInputControl
object. In addition to adding and removing text, that also includes selecting, and replacing text.
The added, selected or removed text can be accessed through a few simple methods on the Change
object.
Method | Description |
---|---|
getControlNewText() | Gets the text that the control will have after the change (if not modified) |
getControlText() | Gets the text content of the control before the change |
getText() | Gets any extra text that’s been added in this change. This includes addition and replacement |
getRangeStart() | See getRangeEnd() |
getRangeEnd() | Methods to get the start and end index (of the current control text) that will be modified. If no text is selected, the start and end will be the same. |
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.

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.
Purpose | Regular 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’.
PseudoclassDescriptionFocussedThe element has the focus of the scene, such as when a TextField is being edited. | |
---|---|
Hover | The state of a component when a mouse is hovering over it. |
Armed | A 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.