Set Up RMI and Hessian Remoting in Spring Boot 2

Java RMI and the Hessian binary web-service format are considered legacy remoting protocols, but they still appear in enterprise codebases that predate REST or in environments where binary efficiency matters more than interoperability. Spring Boot 2 makes both trivially easy to configure through dedicated exporter beans — no XML, no servlet configuration, just a few @Bean declarations. This guide shows you how to expose and consume a service over both protocols with the minimal possible code.

Maven Dependencies

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.18</version>
</parent>

<dependencies>
    <!-- Web layer required by Hessian (HTTP transport) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- Hessian client/server library -->
    <dependency>
        <groupId>com.caucho</groupId>
        <artifactId>hessian</artifactId>
        <version>4.0.66</version>
    </dependency>
</dependencies>

Shared Service Interface

Both RMI and Hessian expose a service through an interface. Keep the interface in a shared module (or JAR) available to both server and client:

package com.ankurm.service;

import java.io.Serializable;
import java.util.List;

// All parameter and return types must be Serializable for RMI
public interface ProductService {
    Product findById(int id);
    List<Product> findAll();
    Product save(Product product);
}

// DTO must implement Serializable for network transport
public class Product implements Serializable {
    private static final long serialVersionUID = 1L;
    private int    id;
    private String name;
    private double price;

    public Product() {}
    public Product(int id, String name, double price) {
        this.id = id; this.name = name; this.price = price;
    }
    // getters and setters...
    public int    getId()    { return id; }
    public String getName()  { return name; }
    public double getPrice() { return price; }
    public void setId(int id)          { this.id = id; }
    public void setName(String name)   { this.name = name; }
    public void setPrice(double price) { this.price = price; }

    @Override public String toString() {
        return "Product{id=" + id + ", name='" + name + "', price=" + price + "}";
    }
}

Service Implementation

package com.ankurm.service;

import org.springframework.stereotype.Service;
import java.util.*;

@Service
public class ProductServiceImpl implements ProductService {

    // In-memory store for demonstration
    private static final Map<Integer, Product> STORE = new LinkedHashMap<>();
    static {
        STORE.put(1, new Product(1, "Laptop",  999.99));
        STORE.put(2, new Product(2, "Monitor", 349.00));
        STORE.put(3, new Product(3, "Keyboard", 59.99));
    }

    @Override
    public Product findById(int id) {
        return STORE.getOrDefault(id,
            new Product(-1, "Not Found", 0));
    }

    @Override
    public List<Product> findAll() {
        return new ArrayList<>(STORE.values());
    }

    @Override
    public Product save(Product product) {
        STORE.put(product.getId(), product);
        return product;
    }
}

Part 1 — Hessian Remoting

Server — Exporting via HessianServiceExporter

import org.springframework.remoting.caucho.HessianServiceExporter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;

import java.util.Map;

@Configuration
public class HessianServerConfig {

    @Autowired
    private ProductService productService;

    // Exporter bean: wraps the service in Hessian serialisation
    @Bean(name = "/hessian/products")
    public HessianServiceExporter hessianProductService() {
        HessianServiceExporter exporter = new HessianServiceExporter();
        exporter.setService(productService);
        exporter.setServiceInterface(ProductService.class);
        return exporter;
    }

    // Map the URL path to the exporter bean
    @Bean
    public HandlerMapping hessianMapping() {
        SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
        mapping.setOrder(1);
        mapping.setUrlMap(Map.of("/hessian/products",
            hessianProductService()));
        return mapping;
    }
}

Client — Consuming with HessianProxyFactoryBean

import org.springframework.remoting.caucho.HessianProxyFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class HessianClientConfig {

    @Bean
    public HessianProxyFactoryBean productServiceProxy() {
        HessianProxyFactoryBean factory = new HessianProxyFactoryBean();
        factory.setServiceUrl("http://localhost:8080/hessian/products");
        factory.setServiceInterface(ProductService.class);
        return factory;
    }
}

// Usage in any Spring component:
@Service
public class OrderService {
    @Autowired ProductService productService; // injected as a Hessian proxy

    public void printAllProducts() {
        productService.findAll().forEach(System.out::println);
    }
}

Part 2 — Java RMI Remoting

Server — Exporting via RmiServiceExporter

import org.springframework.remoting.rmi.RmiServiceExporter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RmiServerConfig {

    @Autowired
    private ProductService productService;

    @Bean
    public RmiServiceExporter rmiProductService() {
        RmiServiceExporter exporter = new RmiServiceExporter();
        exporter.setServiceName("ProductService");   // JNDI name in RMI registry
        exporter.setService(productService);
        exporter.setServiceInterface(ProductService.class);
        exporter.setRegistryPort(1099);              // default RMI port
        return exporter;
    }
}

Client — Consuming with RmiProxyFactoryBean

import org.springframework.remoting.rmi.RmiProxyFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RmiClientConfig {

    @Bean
    public RmiProxyFactoryBean productServiceProxy() {
        RmiProxyFactoryBean factory = new RmiProxyFactoryBean();
        factory.setServiceUrl("rmi://localhost:1099/ProductService");
        factory.setServiceInterface(ProductService.class);
        return factory;
    }
}

RMI vs Hessian Comparison

FeatureJava RMIHessian
TransportCustom TCP (JRMP)HTTP
Firewall friendly?No (custom port)Yes (port 80/443)
Language interopJava onlyMultiple languages
Payload formatJava serialisationBinary (compact)
Load balancer supportDifficultYes (standard HTTP)
Best forInternal Java-to-Java callsCross-firewall binary RPC

See Also

Conclusion

Spring Boot 2 makes both RMI and Hessian remoting a matter of registering a single exporter bean on the server and a single proxy factory bean on the client. The service interface and DTO classes are shared transparently — callers treat the remote service exactly like a local Spring bean. Choose Hessian when you need to cross firewalls or when non-Java clients may appear in the future; choose RMI for pure Java, low-latency internal communication. For any greenfield project, however, a REST or gRPC API is the modern choice with better tooling, observability, and broader ecosystem support.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.