Get up to 80 % extra points for free! More info:

Lesson 24 - Java Chat - Client - Finishing Part 2

In the last lesson, Java Chat - Client - Finishing Part 1, we started working on the GUI of our chat client in Java.

In today's last Java tutorial, we're going to complete the implementation of the chat client and finish the entire course on creating a Java server.

Handling Events to View the Conversation

We'll start by viewing the conversation with the selected user. Double-clicking a user in the ListView will either show a tab with a new conversation or switch to an existing conversation. In the MainController class, we'll create a new constant with an anonymous function handling double-clicks in the ListView:

private final EventHandler <<? super MouseEvent> listContactsClick = event -> {
    final int clickCount = event.getClickCount();
    if (clickCount != 2) {

    final ChatContact contact = lvContactList.getSelectionModel().getSelectedItem();
    if (contact == null) {


At the beginning we check whether we really clicked twice. If not, we don't do anything. Next, we check that we clicked on a contact, not an empty space. If we clicked a contact, we show the conversation using the showConversation() method.

Showing the Selected Conversation

We'll show the selected conversation using the showConversation() method:

private void showConversation(ChatContact contact) {
    final Optional <ChatTab> optionalTab = paneChatContainer.getTabs()
        .filter(tab -> tab.getUserData() == contact)
        .map(tab -> (ChatTab) tab)

    if (optionalTab.isPresent()) {
    } else {

The method accepts, as a parameter, a ChatContact class instance representing the contact we want to display the conversation for. First, we check whether the conversation is already present in the TabPane. If so, we simply switch to it using the select() method. Otherwise, we create a new tab with the conversation and insert it between the other conversations.

Creating a New Conversation

We'll create a new conversation in a separate makeNewTab() method:

private ChatTab makeNewTab(ChatContact chatContact) {
    final ChatTab chatTab = new ChatTab(chatContact);
    return chatTab;

First, we create a new ChatTab class instance. Using the setUserData() method we set the user data. We use this data in the previous code, where we filter the Tab according to the given contact.

Registering a Contact List Handler

Finally, in the initialize() method, we'll register a click handler for the ListView:



Now when we start the server, start the client, and connect to the server, a single contact is displayed in the ListView - Us. We can double-click that contact and a new conversation tab will appear on the right side of the window:

Window with empty conversation in Java chat app

Sending a Message

In the following chapter we'll implement sending a message. We'll only be able to write in the text field if a conversation is selected. The submit button will only be accessible if the text field contains some text. Let's start by implementing these conditions.

Disabling the Components

We'll bind the disable property of the components in the initialize() method. We'll do the button first and bind its disableProperty() to textProperty().isEmpty():


The text box binding is similar:


Submit Message Button Handler

The message will be sent when we press the send button. We already have all the necessary logic implemented, lety's just call it:

private void handleSendMessage(ActionEvent actionEvent) {
    final ChatTab tab = (ChatTab) paneChatContainer.getSelectionModel().getSelectedItem();
    if (tab == null) {

    final String id = ((ChatContact) tab.getUserData()).getId();
    final String message = txtMessage.getText();
    chatService.sendMessage(id, message);

First, we test whether a Tab open. If not, the method does nothing. The contact ID is obtained from the user data from the Tab and the message contents from the text field. Finally, we call our service to take care of sending the message. This is followed by clearing the text field and requesting focus so we can type again.

Displaying Messages

Received messages already have all the logic implemented so they are automatically displayed when they are sent. If the conversation isn't visible, the number of unread messages will appear in the contact list next to the contact name.

Typing Information

Now we'll implement a function that will inform the user whether or not the client at the other end is typing. In the MainController class, we'll create a new class constant with an anonymous function that will be called when the contents of the message text field is changed:

private ChangeListener <<? super String> messageContentListener = (observable, oldValue, newValue) -> {
    final ChatTab tab = (ChatTab) paneChatContainer.getSelectionModel().getSelectedItem();
    if (tab == null) {

    final String id = ((ChatContact) tab.getUserData()).getId();
    chatService.notifyTyping(id, !newValue.isEmpty());

It's important to call the notifyTyping() method on the last line of the function to inform whether the user is typing or not.

Tab Switching Handling

It's nice that we informed the user that we're typing, but what if we switch to another conversation as we type because we realize that the message is for someone else. So we'll create a listener responding to the conversation change. We'll create another class constant with an anonymous function:

private ChangeListener <<? super Tab> tabChangeListener = (observable, oldValue, newValue) -> {
    if (oldValue != null) {
        final ChatTab oldTab = (ChatTab) oldValue;
        final String id = ((ChatContact) oldTab.getUserData()).getId();
        chatService.notifyTyping(id, false);

    if (newValue != null) {
        if (!txtMessage.getText().isEmpty()) {
            final ChatTab newTab = (ChatTab) newValue;
            final String id = ((ChatContact) newTab.getUserData()).getId();
            chatService.notifyTyping(id, true);
    } else {

The function receives oldValue (the old conversations) and newValue (new conversation) as parameters. If we're really switching from the old conversation, oldValue won't be null and we inform the user in the old conversation that we're no longer typing a message for him. If we switched to a new conversation and the message text field contains text, we inform the new user that we're probably typing a message for him.

Registering the Listener

Finally, we'll register the listener for changing the conversation in the initialize() method:


Testing the Typing Indicator

Now when we open a conversation and start typing, the Tab icon will turn into a very simple animation.


Below you can see the resulting chat client. There's the number of unread messages displayed next to contacts. In addition, the typing indicator appears in the tab. If the conversation is longer, a scrollbar appears:

Conversation example

The End

By this we've finished the whole course on creating a simple Java chat client. Of course, the client deserves a number of improvements, which I leave up to you, the readers. I hope you enjoyed the series and if you have any comments, there's a comment section below.


Previous article
Java Chat - Client - Finishing Part 1
All articles in this section
Server for Client Applications in Java
Article has been written for you by Petr Štechmüller
User rating:
No one has rated this quite yet, be the first one!