Backend Development 20 min read

Mastering Event‑Driven IO: Reactor vs. Proactor Patterns in Java

This article explains the design of event‑driven architectures for high‑performance web services, compares the Reactor (synchronous) and Proactor (asynchronous) models, and provides Java‑style pseudocode for their core components, handlers, and event loops.

Xiaokun's Architecture Exploration Notes
Xiaokun's Architecture Exploration Notes
Xiaokun's Architecture Exploration Notes
Mastering Event‑Driven IO: Reactor vs. Proactor Patterns in Java

Event‑Driven Architecture (EDA) Components

Event source / emitter: polls for state changes.

Demultiplexer: collects ready events from sources and forwards them to handlers.

Event handlers: user‑defined callbacks that process ready events.

Event queue (channel): stores registered events; when an event becomes ready, the demultiplexer retrieves it.

Reactor Design Principle

The Reactor separates the event‑loop (polling) from the business logic. The Reactor component monitors I/O readiness, while Handler objects (e.g., RequestHandler and Acceptor ) process the events.

<code>class Client {
    public static void main(String[] args) {
        Button button = new Button();
        button.bind("click", new ClickHandler());
        button.click();
    }
}
</code>
<code>interface ActionHandler {
    void handler(ActionEvent e);
}

class ClickListener implements ActionHandler {
    @Override
    public void handler(ActionEvent e) { }
}
</code>
<code>class Button extends ActionListener {
    void click() { this.trigger("click"); }
    void bind(String type, ActionHandler handler) { this.store(this, type, handler); }
}
</code>
<code>class ActionListener {
    private Map<String, ActionEvent> events = new HashMap<>();
    public void store(Object source, String type, ActionHandler handler) { events.put(type, new ActionEvent(source, handler, type)); }
    public void trigger(String type) {
        ActionEvent event = map.get(type);
        Object target = event.getTarget();
        Method callback = target.getClass().getDeclaredMethod("handler", ActionEvent.class);
        callback.invoke(target, event);
    }
}
</code>
<code>class ActionEvent {
    private Object source;
    private Object target;
    private String eventType;
    // other fields such as status, timestamp, id …
}
</code>

The article then shows a complete NIO event flow for a web server, including Accept and Read events, and explains how the Reactor processes them.

Reactor Implementation Sketch

<code>class NIOReactorServer {
    public static void main(String[] args) {
        ServerSocketChannel server = ServerSocketChannel.open();
        server.bind(new InetSocketAddress(port), MaxBackLogs);
        Reactor reactor = new Reactor();
        Handler request = new RequestHandler();
        reactor.register(ACCEPT, server, new Acceptor(request));
        reactor.handle_events();
    }
}
</code>
<code>abstract class Handler {
    public void acceptorHandler(SelectableChannel server) { }
    public void requestHandler(SelectableChannel client) { }
}

class Acceptor extends Handler {
    private Handler handler;
    public Acceptor(Handler handler) { this.handler = handler; }
    public void acceptorHandler(SelectionKey key) {
        Socket client = key.channel().accept();
        reactor.register(READ, client, handler);
    }
}
</code>
<code>class Reactor {
    private Map<SelectionKey, Invoker> acceptMap = new ConcurrentHashMap<>();
    private Map<SelectionKey, Invoker> readMap = new ConcurrentHashMap<>();
    private Selector demultiplexer;
    public Reactor() { this.demultiplexer = Selector.open(); }
    public void register(String type, SelectableChannel socket, Handler handler) { /* register with kernel */ }
    public void handle_events() {
        while (true) {
            Set<SelectionKey> readySet = demultiplexer.select();
            for (SelectionKey key : readySet) {
                if (acceptMap.containsKey(key)) dispatch(ACCEPT, key);
                else if (readMap.containsKey(key)) dispatch(READ, key);
            }
            readySet.clear();
        }
    }
    private void dispatch(String type, SelectionKey key) { /* invoke appropriate handler */ }
}
</code>

Proactor Design Principle

The Proactor uses asynchronous I/O (AIO) where the kernel performs the operation and notifies the application upon completion. The article lists the POSIX AIO API (e.g., aio_read , aio_write , aio_error ) and shows the structure of struct aiocb .

<code>struct aiocb {
    int aio_fildes;      // file descriptor
    off_t aio_offset;    // file offset
    volatile void *aio_buf; // buffer
    size_t aio_nbytes;   // buffer size
    int aio_reqprio;     // priority
    struct sigevent aio_sigevent; // completion notification
    int aio_lio_opcode;  // operation type (for lio_listio)
};
</code>

Proactor Implementation Sketch

<code>class NIOServer {
    final Queue<CompletionEvent<Object>> completedEventQueue = new ConcurrentLinkedQueue<>();
    AsyncOperactionProcessor processor = new AsyncOperactionProcessor(){
        public void asyncAccept(AsyncChannel ch, Handler h, Object a){
            CompletionEvent e = new CompletionEvent(ch, h, a);
            e.setResult(ch);
            completedEventQueue.add(e);
        }
        public void asyncRead(AsyncChannel ch, Handler h, ByteBuffer buf, Object a){
            CompletionEvent e = new CompletionEvent(ch, h, a);
            ch.read(buf);
            e.setResult(buf.remaining());
        }
    };
    AsyncServerSocketChannel server = AsyncServerSocketChannel.open().bind(9999);
    Proactor proactor = new Proactor(server, processor, new Acceptor(server, processor));
    proactor.handle_events();
}
</code>
<code>class Proactor {
    private AsyncOperactionProcessor processor;
    private Handler handler;
    private AsyncServerSocketChannel server;
    public Proactor(AsyncServerSocketChannel s, AsyncOperactionProcessor p, Handler h){
        this.server = s; this.processor = p; this.handler = h; }
    public void handle_events(){
        while (true){
            Future<Queue<CompletionEvent>> result = server.accept(processor, handler);
            Queue<CompletionEvent> q = result.get();
            CompletionEvent ev;
            while ((ev = q.poll()) != null) dispatch(ev);
        }
    }
    private void dispatch(CompletionEvent ev){
        Method m = ev.getHandler().getClass().getDeclaredMethod("completed", ev.getResultClass(), ev.getAttachmentClass());
        m.invoke(ev.getHandler(), ev.getResult(), ev.getAttachment());
    }
}
</code>

Both patterns share the same high‑level idea—separating I/O readiness detection from business logic—but differ in the I/O model: Reactor relies on synchronous non‑blocking I/O, while Proactor leverages true asynchronous I/O provided by the kernel.

Libraries Supporting These Patterns

ACE framework – provides Reactor and Proactor implementations.

Boost.Asio – a C++ library based on the Proactor model.

TProactor – a teaching implementation of Proactor.

References to Java NIO documentation are also listed for further reading.

Reactor Patternevent-drivenasynchronous ioJava NIOproactor pattern
Xiaokun's Architecture Exploration Notes
Written by

Xiaokun's Architecture Exploration Notes

10 years of backend architecture design | AI engineering infrastructure, storage architecture design, and performance optimization | Former senior developer at NetEase, Douyu, Inke, etc.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.