Backend Development 14 min read

How Electron’s remote Module Mimics Java RMI to Seamlessly Share Objects Between Processes

This article explains the two inter‑process communication methods in Electron, demonstrates why the remote module behaves like a reference rather than a copy, and reveals its internal implementation that mirrors Java RMI by wrapping objects and functions as remote proxies using synchronous IPC messages.

Tencent IMWeb Frontend Team
Tencent IMWeb Frontend Team
Tencent IMWeb Frontend Team
How Electron’s remote Module Mimics Java RMI to Seamlessly Share Objects Between Processes

Two Process Communication Methods in Electron

Electron provides two ways to communicate between the main and renderer processes: using the

ipcMain

and

ipcRenderer

modules, or using the

remote

module.

Using IPC Modules

Example main‑process code:

<code>const remoteObj = { name: 'remote' };
const getRemoteObject = event => {
  // after 1 second modify the object
  setTimeout(() => {
    remoteObj.name = 'modified name';
    win.webContents.send('modified');
  }, 1000);
  event.returnValue = remoteObj;
};
app.getRemoteObject = getRemoteObject;</code>

Renderer‑process code:

<code>const { ipcRenderer } = require('electron');
const container = document.querySelector('#container');
const remoteObj = ipcRenderer.sendSync('getRemoteObject');
container.innerText = `Before modified\n${JSON.stringify(remoteObj, null, '    ')}`;
ipcRenderer.on('modified', () => {
  container.innerText = `After modified\n${JSON.stringify(remoteObj, null, '    ')}`;
});</code>

The output shows that the renderer receives a copy of the object; after the main process changes the value, the renderer’s copy does not update automatically.

Using the remote Module

Example main‑process code:

<code>const remoteObj = { name: 'remote' };
const getRemoteObject = event => {
  setTimeout(() => {
    remoteObj.name = 'modified name';
    win.webContents.send('modified');
  }, 1000);
  return remoteObj;
};
app.getRemoteObject = getRemoteObject;</code>

Renderer‑process code:

<code>const { remote } = require('electron');
const remoteObj = remote.app.getRemoteObject();
// remoteObj now references the same object in the main process
</code>

The result shows that the renderer can access the same object instance, and changes made in the main process are reflected instantly.

Why remote Works Like RMI

The

remote

module still uses IPC under the hood, but it converts every transferred

Object

into a remote reference, similar to Java’s Remote Method Invocation (RMI). This hides the message‑passing details from developers.

Java RMI Overview

RMI allows a client to invoke methods on a remote object as if it were local. It uses a registry (like DNS) to bind a name to a server‑side object and communicates via the JRMP protocol.

Simple RMI implementation in Java:

<code>public interface HelloRMI extends Remote {
    String sayHi(String name) throws RemoteException;
}

public class HelloRMIImpl extends UnicastRemoteObject implements HelloRMI {
    protected HelloRMIImpl() throws RemoteException { super(); }
    public String sayHi(String name) throws RemoteException {
        System.out.println("Server: Hi " + name + " " + getClientHost());
        return "Server";
    }
}

public class Server {
    public static void main(String[] args) {
        try {
            HelloRMI hr = new HelloRMIImpl();
            LocateRegistry.createRegistry(9999);
            Naming.bind("rmi://localhost:9999/hello", hr);
            System.out.println("RMI Server bind success");
        } catch (Exception e) { e.printStackTrace(); }
    }
}

public class Client {
    public static void main(String[] args) {
        try {
            HelloRMI hr = (HelloRMI) Naming.lookup("rmi://localhost:9999/hello");
            System.out.println("Client: Hi " + hr.sayHi("Client"));
        } catch (Exception e) { e.printStackTrace(); }
    }
}
</code>

Electron remote Implementation Details

The remote module adds built‑in properties that forward requests to the main process via synchronous IPC messages.

<code>const addBuiltinProperty = name => {
  Object.defineProperty(exports, name, {
    get: () => exports.getBuiltin(name)
  });
};

const browserModules = require('../../common/api/module-list')
  .concat(require('../../browser/api/module-list'))
  .filter(m => !m.private)
  .map(m => m.name)
  .forEach(addBuiltinProperty);

exports.getBuiltin = module => {
  const command = 'ELECTRON_BROWSER_GET_BUILTIN';
  const meta = ipcRenderer.sendSync(command, module);
  return metaToValue(meta);
};

function metaToValue(meta) {
  const types = {
    value: () => meta.value,
    array: () => meta.members.map(member => metaToValue(member)),
    buffer: () => bufferUtils.metaToBuffer(meta.value),
    promise: () => resolvePromise({ then: metaToValue(meta.then) }),
    error: () => { throw metaToException(meta); },
    date: () => new Date(meta.value),
    exception: () => { throw metaToException(meta); }
  };
  if (meta.type in types) return types[meta.type]();

  if (remoteObjectCache.has(meta.id)) return remoteObjectCache.get(meta.id);

  if (meta.type === 'function') {
    const remoteFunction = function(...args) {
      const command = (this && this.constructor === remoteFunction)
        ? 'ELECTRON_BROWSER_CONSTRUCTOR'
        : 'ELECTRON_BROWSER_FUNCTION_CALL';
      const obj = ipcRenderer.sendSync(command, meta.id, wrapArgs(args));
      return metaToValue(obj);
    };
    const ret = remoteFunction;
    setObjectMembers(ret, ret, meta.id, meta.members);
    setObjectPrototype(ret, ret, meta.id, meta.proto);
    Object.defineProperty(ret.constructor, 'name', { value: meta.name });
    v8Util.setRemoteObjectFreer(ret, meta.id);
    v8Util.setHiddenValue(ret, 'atomId', meta.id);
    remoteObjectCache.set(meta.id, ret);
    return ret;
  }

  // object handling
  const ret = {};
  setObjectMembers(ret, ret, meta.id, meta.members);
  setObjectPrototype(ret, ret, meta.id, meta.proto);
  v8Util.setRemoteObjectFreer(ret, meta.id);
  v8Util.setHiddenValue(ret, 'atomId', meta.id);
  remoteObjectCache.set(meta.id, ret);
  return ret;
}
</code>

Both objects and functions are cached in

remoteObjectCache

so that repeated accesses return the same proxy, providing reference‑like behavior.

Conclusion

The

remote

module not only abstracts IPC but also wraps main‑process objects into remote proxies, achieving reference semantics similar to Java RMI. This explains why modifications in the main process are instantly visible in the renderer and why the module can expose main‑process APIs without explicit message handling.

ElectronNode.jsIPCRMIProcess Communicationremote module
Tencent IMWeb Frontend Team
Written by

Tencent IMWeb Frontend Team

IMWeb Frontend Community gathering frontend development enthusiasts. Follow us for refined live courses by top experts, cutting‑edge technical posts, and to sharpen your frontend skills.

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.