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
| Feature | Java RMI | Hessian |
|---|---|---|
| Transport | Custom TCP (JRMP) | HTTP |
| Firewall friendly? | No (custom port) | Yes (port 80/443) |
| Language interop | Java only | Multiple languages |
| Payload format | Java serialisation | Binary (compact) |
| Load balancer support | Difficult | Yes (standard HTTP) |
| Best for | Internal Java-to-Java calls | Cross-firewall binary RPC |
See Also
- Building a REST API with Spring Boot
- JMS Deep Dive: Asynchronous Messaging in Java
- Resilience4j Circuit Breaker in Spring Boot
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.