Why doesn't this simple chat server work?

I was trying to play around with Sockets. The idea of a chat server is from Head First Java, but I decided it would be better to try to implement it from scratch instead of copy-pasting Katty Sierra’s “oven-ready” code. Note it’s as simple as it may ever get. The goal is to get more comfortable with bare-bones Sockets rather than making a reasonable chat implementation. I tried to keep it short, but you may still have to go through some code. To test it, you first need to run the server and then start the client

package org.example.demos.sockets.simpleChat;

public class SimpleChatClient {
    public static void main(String[] args) {
        SimpleChat chat = new SimpleChat();
        chat.start();
    }
}
package org.example.demos.sockets.simpleChat;

import com.seryozha.commons.UIUtil;
import com.seryozha.commons.Util;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER;
import static javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED;

@Slf4j
public class SimpleChat {
    private final Socket socket = Util.wrapInTryCatchAndGet(() -> new Socket("localhost", 5000));
    private final BufferedReader reader = Util.wrapInTryCatchAndGet(() -> new BufferedReader(new InputStreamReader(socket.getInputStream())));
    private final PrintWriter writer = Util.wrapInTryCatchAndGet(() -> new PrintWriter(socket.getOutputStream()));
    private JTextField messageField;
    private JTextArea chatArea;
    public void start() {
        UIUtil.getUIBuilder()
                .withTitle("Simple Chat")
                .withComponent(() -> UIUtil.getComponentBuilder(() -> {
                            var mainPanel = new JPanel();
                            var layout = new BoxLayout(mainPanel, BoxLayout.Y_AXIS);
                            mainPanel.setLayout(layout);
                            return mainPanel;
                        })
                        .withComponent(() -> UIUtil.getComponentBuilder(() -> {
                                    chatArea = new JTextArea(15, 20);
                                    chatArea.setLineWrap(true);
                                    chatArea.setWrapStyleWord(true);
                                    chatArea.setEditable(false);
                                    return chatArea;
                                })
                                .withHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_NEVER)
                                .withVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_AS_NEEDED)
                                .build())
                        .withComponent(() -> UIUtil.getComponentBuilder(() -> {
                                    messageField = new JTextField(20);
                                    return messageField;
                                })
                                .withHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_NEVER)
                                .withVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_AS_NEEDED)
                                .build())
                        .withComponent(() -> {
                            var sendButton = new JButton("Send");
                            sendButton.addActionListener(new SendButtonListener());
                            return sendButton;
                        })
                        .withHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_NEVER)
                        .withVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_AS_NEEDED)
                        .build())
                .withFrameSize(500, 650)
                .visualize();

        startReadingThread();
    }

    private void startReadingThread() {
        new Thread(this::readFromSocketAndUpdate).start();
    }

    @SneakyThrows
    private void readFromSocketAndUpdate() {
        while (true) {
            String message = reader.readLine();
            if (message != null) { // never true!
                log.debug("message: " + message);
                chatArea.append(message + "\n");
            }
        }
    }

    private class SendButtonListener implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {
            log.debug("actionPerformed() triggered...");
            writer.println(messageField.getText());
            writer.flush();
            messageField.setText("");
        }
    }
}
package org.example.demos.sockets.simpleChat;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Slf4j
public class SimpleChatServer {
    private static final List<String> cachedMessages = new CopyOnWriteArrayList<>();
    private static final ExecutorService executorService = Executors.newCachedThreadPool();
    private static final List<PrintWriter> writerList = new ArrayList<>();

    public static void main(String[] args) {
        start();
    }

    @SneakyThrows
    private static void start() {
        try (var serverSocket = new ServerSocket(5000)) {
            while (true) {
                Socket socket = serverSocket.accept();
                executorService.submit(() -> serviceClient(socket));
            }
        }
    }

    @SneakyThrows
    private static void serviceClient(Socket socket) {
        registerWriter(socket);
        loadSentMessages(socket);
        var reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        while (true) {
            String line = reader.readLine();
            if (line != null) {
                log.debug("line is not null");
                cachedMessages.add(line);
                broadcast(line);
            }
        }
    }

    private static void registerWriter(Socket socket) throws IOException {
        writerList.add(new PrintWriter(socket.getOutputStream()));
    }

    private static void loadSentMessages(Socket socket) throws IOException {
        var writer = new PrintWriter(socket.getOutputStream());
        cachedMessages.forEach(writer::println);
    }

    private static void broadcast(String line) {
        log.debug("broadcast() invoked...");
        log.debug("writerList.size(): " + writerList.size());
        writerList.forEach(writer -> writer.println(line));
    }
}

To get that “seryozha.commons” thing to work, you need this in your pom.xml (or some Gradle equivalent, if you prefer it)

    <repositories>
        <repository>
            <id>jitpack.io</id>
            <url>https://jitpack.io</url>
        </repository>
    </repositories>

    <dependencies>
        <dependency>
            <groupId>com.github.NadChel</groupId>
            <artifactId>commons</artifactId>
            <version>-SNAPSHOT</version>
        </dependency>

The problem: once I print a message and press “Send”, it just disappears. The UI is never updated

Here are my server logs (obviously, the format may look different in your case, depending on your logging configuration):

---- 29 сент. 2023 18:04:01,976
DEBUG [pool-2-thread-1] : line is not null

---- 29 сент. 2023 18:04:01,982
DEBUG [pool-2-thread-1] : broadcast() invoked...

---- 29 сент. 2023 18:04:01,984
DEBUG [pool-2-thread-1] : writerList.size(): 1

Client logs:

---- 29 сент. 2023 18:04:01,961
DEBUG [AWT-EventQueue-0] : actionPerformed() triggered...

For some reason, my “reading thread” can’t find any anything in the input stream (even though the server sent the message back)

Why?

It’s because you need to flush() on the server side too. Change this

        writerList.forEach(writer -> writer.println(line));

to this:

        writerList.forEach(writer -> {
            writer.println(line);
            writer.flush();
        });

You may also want to change your loadSentMessages() method:

        cachedMessages.forEach(message -> {
            writer.println(message);
            writer.flush();
        });