eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

Course – LJB – NPI EA (cat = Core Java)
announcement - icon

Code your way through and build up a solid, practical foundation of Java:

>> Learn Java Basics

Partner – LambdaTest – NPI EA (cat= Testing)
announcement - icon

Distributed systems often come with complex challenges such as service-to-service communication, state management, asynchronous messaging, security, and more.

Dapr (Distributed Application Runtime) provides a set of APIs and building blocks to address these challenges, abstracting away infrastructure so we can focus on business logic.

In this tutorial, we'll focus on Dapr's pub/sub API for message brokering. Using its Spring Boot integration, we'll simplify the creation of a loosely coupled, portable, and easily testable pub/sub messaging system:

>> Flexible Pub/Sub Messaging With Spring Boot and Dapr

1. Introduction

In this tutorial, we’ll explore how to send and receive serialized objects using Java’s SocketChannel from the java.nio package. This approach enables efficient, non-blocking network communication between a client and a server.

2. Understanding Serialization

Serialization is the process of converting an object into a byte stream, allowing it to be transmitted over a network or stored in a file. When combined with socket channels, serialization enables the seamless transfer of complex data structures between applications. This technique is essential for distributed systems where objects must be exchanged over a network.

2.1. Key Classes in Java Serialization

The ObjectOutputStream and ObjectInputStream classes are essential in Java serialization. They handle the conversion between objects and byte streams:

  • ObjectOutputStream is used to serialize an object into a sequence of bytes. For example, when sending a Message object over a network, ObjectOutputStream writes the object’s fields and metadata into an output stream.
  • ObjectInputStream reconstructs the object from the byte stream on the receiving side.

3. Understanding Socket Channels

Socket channels are part of Java’s NIO package, which offers a flexible, scalable alternative to traditional socket-based communication. They support both blocking and non-blocking modes, making them suitable for high-performance network applications where handling multiple connections efficiently is crucial.

A socket channel is essential for creating a client-server communication system, where the client can connect to a server over TCP/IP. By using SocketChannel, we can implement asynchronous communication that allows for better performance and lower latency.

3.1. Key Components of Socket Channels

There are three key components of socket channels:

  • ServerSocketChannel: Listens for incoming TCP connections. It binds to a specific port and waits for clients to connect
  • SocketChannel: Represents a connection between a client and server. It supports both blocking and non-blocking modes
  • Selector: Used to monitor multiple socket channels with a single thread. It helps handle events like incoming connections or data being readable, reducing the overhead of having a dedicated thread for each connection.

4. Setting up the Server and Client

Before implementing the server and client, let’s first define a sample object that we want to send over the socket. In Java, an object must implement the Serializable interface to be converted into a byte stream, which is necessary for transmitting it over a network connection.

4.1. Creating a Serializable Object

Let’s write the MyObject class, which serves as an example of a serializable object that we’ll be sending and receiving through a SocketChannel:

class MyObject implements Serializable {
    private String name;
    private int age;

    public MyObject(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

The MyObject class implements the Serializable interface, which is required for the object to be converted into a byte stream and transmitted over a socket connection.

4.2. Implementing the Server

On the server side, we’ll use ServerSocketChannel to listen for incoming client connections and handle the received serialized objects:

private static final int PORT = 6000;

try (ServerSocketChannel serverSocket = ServerSocketChannel.open()) {
    serverSocket.bind(new InetSocketAddress(PORT));
    logger.info("Server is listening on port " + PORT);

    while (true) {
        try (SocketChannel clientSocket = serverSocket.accept()) {
            System.out.println("Client connected...");
            // To receive object here
        }
    }
} catch (IOException e) {
    // handle exception
}

The server listens for incoming client connections on port 6000. Upon accepting a client, it will wait for an object to be received.

4.3. Implementing the Client

The client will create an instance of MyObject, serialize it, and send it to the server. We use SocketChannel to connect to the server and transmit the object:

private static final String SERVER_ADDRESS = "localhost";
private static final int SERVER_PORT = 6000;

try (SocketChannel socketChannel = SocketChannel.open()) {
    socketChannel.connect(new InetSocketAddress(SERVER_ADDRESS, SERVER_PORT));
    logger.info("Connected to the server...");

    // To send object here
} catch (IOException e) {
   // handle exception
}

This code connects to the server running on the localhost at port 6000, where it will send the serialized object to the server.

5. Serializing and Sending the Object

To transmit an object via a SocketChannel, we’ll serialize it into a byte array and wrap it in a ByteBuffer. Before sending the serialized data, we also prepend a 4-byte integer to indicate the length of the byte array.

This ensures the receiver knows how many bytes to read for the full object:

void sendObject(SocketChannel channel, MyObject obj) throws IOException {
    ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
    try (ObjectOutputStream objOut = new ObjectOutputStream(byteStream)) {
        objOut.writeObject(obj);
    }
    byte[] bytes = byteStream.toByteArray();

    ByteBuffer lengthBuffer = ByteBuffer.allocate(4);
    lengthBuffer.putInt(bytes.length);
    lengthBuffer.flip();
    while (lengthBuffer.hasRemaining()) {
        channel.write(lengthBuffer);
    }

    ByteBuffer dataBuffer = ByteBuffer.wrap(bytes);
    while (dataBuffer.hasRemaining()) {
        channel.write(dataBuffer);
    }
}

Here, we first serialize the MyObject into a byte array, then wrap it into a ByteBuffer, and write it to the socket channel. Then, we send the object from the client:

try (SocketChannel socketChannel = SocketChannel.open()) {
    socketChannel.connect(new InetSocketAddress(SERVER_ADDRESS, SERVER_PORT));
    MyObject objectToSend = new MyObject("Alice", 25);
    sendObject(socketChannel, objectToSend); // Serialize and send
}

In this example, the client connects to the server and sends a serialized MyObject containing the name “Alice” and age 25.

6. Receiving and Deserializing the Object

On the server side, we first read the 4-byte length, then read the bytes from the SocketChannel and deserialize it into a MyObject instance:

MyObject receiveObject(SocketChannel channel) throws IOException, ClassNotFoundException {
    ByteBuffer lengthBuffer = ByteBuffer.allocate(4);
    while (lengthBuffer.hasRemaining()) {
        if (channel.read(lengthBuffer) == -1) {
            throw new EOFException("Connection closed prematurely");
        }
    }
    lengthBuffer.flip();
    int length = lengthBuffer.getInt();

    // Read exactly 'length' bytes
    ByteBuffer dataBuffer = ByteBuffer.allocate(length);
    while (dataBuffer.hasRemaining()) {
        if (channel.read(dataBuffer) == -1) {
            throw new EOFException("Incomplete data received");
        }
    }
    dataBuffer.flip();

    byte[] bytes = new byte[length];
    dataBuffer.get(bytes);
    try (ObjectInputStream objIn = new ObjectInputStream(new ByteArrayInputStream(bytes))) {
        return (MyObject) objIn.readObject();
    }
}

We read the bytes from the SocketChannel into a ByteBuffer, store them in a ByteArrayOutputStream, and then deserialize the byte array into the original object. Then, we can receive the object on the server:

try (SocketChannel clientSocket = serverSocket.accept()) {
    MyObject receivedObject = receiveObject(clientSocket);
    logger.info("Received Object - Name: " + receivedObject.getName());
}

7. Handling Multiple Clients

To handle multiple clients concurrently, we can use a Selector to manage multiple socket channels in non-blocking mode. This ensures that the server can handle many connections simultaneously without blocking any single connection:

class NonBlockingServer {
    private static final int PORT = 6000;

    public static void main(String[] args) throws IOException {
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.bind(new InetSocketAddress(PORT));
        serverChannel.configureBlocking(false);

        Selector selector = Selector.open();
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
            selector.select();
            Set<SelectionKey> selectedKeys = selector.selectedKeys();
            Iterator<SelectionKey> iter = selectedKeys.iterator();

            while (iter.hasNext()) {
                SelectionKey key = iter.next();
                iter.remove();

                if (key.isAcceptable()) {
                    SocketChannel client = serverChannel.accept();
                    client.configureBlocking(false);
                    client.register(selector, SelectionKey.OP_READ);
                } else if (key.isReadable()) {
                    SocketChannel client = (SocketChannel) key.channel();
                    MyObject obj = receiveObject(client);
                    System.out.println("Received from client: " + obj.getName());
                }
            }
        }
    }
}

In this example, configureBlocking(false) sets the server in non-blocking mode, meaning that operations like accept() and read() won’t block the execution while waiting for events. This allows the server to continue processing other tasks instead of getting stuck waiting for a client to connect.

Next, we use a Selector to listen for events on multiple channels. It detects when a new connection (OP_ACCEPT) or incoming data (OP_READ) is available and processes them accordingly, ensuring smooth and scalable communication.

8. Test Cases

Let’s validate the serialization and deserialization of objects over SocketChannel:

@Test
void givenClientSendsObject_whenServerReceives_thenDataMatches() throws Exception {
    try (ServerSocketChannel server = ServerSocketChannel.open().bind(new InetSocketAddress(6000))) {
        int port = ((InetSocketAddress) server.getLocalAddress()).getPort();
        ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<MyObject> future = executor.submit(() -> {
            try (SocketChannel client = server.accept();
                 ObjectInputStream objIn = new ObjectInputStream(Channels.newInputStream(client))) {
                return (MyObject) objIn.readObject();
            }
        });

        try (SocketChannel client = SocketChannel.open()) {
            client.configureBlocking(true);
            client.connect(new InetSocketAddress("localhost", 6000));

            while (!client.finishConnect()) {
                Thread.sleep(10);
            }

            try (ObjectOutputStream objOut = new ObjectOutputStream(Channels.newOutputStream(client))) {
                objOut.writeObject(new MyObject("Test User", 25));
            }
        }

        MyObject received = future.get(2, TimeUnit.SECONDS);
        assertEquals("Test User", received.getName());
        assertEquals(25, received.getAge());
        executor.shutdown();
    }
}

This test validates that the serialization and deserialization process works correctly over a SocketChannel.

9. Conclusion

In this article, we demonstrated how to set up a client-server system using Java NIO’s SocketChannel to send and receive serialized objects. By using serialization and non-blocking I/O, we can efficiently transmit complex data structures between systems over a network.

The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.
Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

Once the early-adopter seats are all used, the price will go up and stay at $33/year.

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

eBook Jackson – NPI EA – 3 (cat = Jackson)
2 Comments
Oldest
Newest
Inline Feedbacks
View all comments