Migrate Spring RMI and Hessian Remoting to an HTTP Service

Replace deprecated Spring RMI and Hessian remoting with a Spring Boot 4 HTTP endpoint and typed client, with tested success and 404 behavior.

Spring’s RMI and Hessian integration was deprecated in Spring Framework 5.3 and removed in the next major release. Its exporter and proxy factory classes therefore cannot be used in a Spring Boot 4 application. Java RMI as a Java technology is separate from that Spring integration and still exists, but the old Spring configuration in this article is no longer a current Boot example.

This tutorial shows how to move the same responsibility—one application asking another for information—to an HTTP endpoint and a typed HTTP service client. The example uses an inventory service that reports available stock to an ordering application. It runs on Spring Boot 4.1.2 and Java 17 or later.

1. Understand the Migration Boundary

Spring’s old remoting support let a server export a Java service and let a client call it through a proxy. With RMI, RmiServiceExporter and RmiProxyFactoryBean handled that connection. Hessian used HessianServiceExporter and HessianProxyFactoryBean over a binary HTTP protocol. Those classes are historical Spring integrations; they are absent from current Framework 7.

The current example exposes an HTTP resource with a Spring MVC controller and calls it through a Java interface backed by RestClient. This keeps a typed client method, but it is a protocol migration, not a drop-in or wire-compatible replacement. An existing RMI or Hessian client must be changed to call the new HTTP endpoint.

Responsibility Old Spring remoting Current HTTP example
Publish server operation RMI or Hessian exporter Spring MVC controller
Define client contract Shared Java service interface HTTP service interface with GetExchange
Create client proxy RMI or Hessian proxy factory bean HttpServiceProxyFactory backed by RestClient
Exchange data Java or Hessian serialization HTTP response with JSON

2. Add Spring Boot 4 Dependencies

The server needs Spring MVC. The client needs RestClient, which performs the HTTP request behind the interface proxy. The test starter supplies the embedded-server test utilities. Spring Boot’s parent manages dependency versions.

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>4.1.2</version>
  <relativePath/>
</parent>
<properties><java.version>17</java.version></properties>
<dependencies>
  <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webmvc</artifactId></dependency>
  <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-restclient</artifactId></dependency>
  <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webmvc-test</artifactId><scope>test</scope></dependency>
</dependencies>

The runnable project combines server and client code to keep the example small. In deployment, they would normally be separate applications with a stable HTTP contract between them.

3. Expose the Inventory Endpoint

The inventory application owns the stock count. Its GET /inventory/{sku} endpoint returns a JSON representation of a known SKU. For an unknown SKU, it returns HTTP 404. This explicit status is part of the remote contract, unlike a local Java method call.

@RestController
@RequestMapping("/inventory")
public class InventoryController {
    public record Stock(String sku, int available) {}

    @GetMapping("/{sku}")
    public ResponseEntity<Stock> stock(@PathVariable String sku) {
        if (!"BOOK-1".equals(sku)) {
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.ok(new Stock(sku, 7));
    }
}

For BOOK-1, the HTTP response is 200 with JSON equivalent to {"sku":"BOOK-1","available":7}. The record defines the data transferred over the network. It does not transfer the server’s Java service object.

4. Call the Endpoint Through a Typed Interface

The ordering application declares the remote operation as an HTTP service interface. GetExchange maps stock() to the server’s GET endpoint, and PathVariable supplies the SKU. The interface remains a Java programming contract while the transport is HTTP.

public interface InventoryClient {
    @GetExchange("/inventory/{sku}")
    InventoryController.Stock stock(@PathVariable String sku);
}

Create a RestClient with the inventory server’s base URL. RestClientAdapter connects it to HttpServiceProxyFactory, which creates the implementation of InventoryClient. A call to stock(“BOOK-1”) then performs an HTTP request and decodes the JSON response.

RestClient restClient = RestClient.builder()
        .baseUrl("http://localhost:" + port)
        .build();

InventoryClient client = HttpServiceProxyFactory
        .builderFor(RestClientAdapter.create(restClient))
        .build()
        .createClient(InventoryClient.class);

InventoryController.Stock stock = client.stock("BOOK-1");
// stock is Stock[sku=BOOK-1, available=7]

The example constructs the proxy explicitly so its parts are visible. In an application with many interfaces, Spring Framework 7 can register HTTP service clients as beans through its HTTP service group configuration. For the underlying synchronous client API, see our Spring RestClient guide.

5. Verify a Real HTTP Round Trip

The integration test starts the Boot application on a random port. It creates the client against that port and calls the real embedded server. This verifies the mapping, JSON conversion and client proxy together.

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class InventoryClientTest {
    @Value("${local.server.port}")
    int port;

    private InventoryClient client() {
        RestClient restClient = RestClient.builder()
                .baseUrl("http://localhost:" + port).build();
        return HttpServiceProxyFactory
                .builderFor(RestClientAdapter.create(restClient))
                .build().createClient(InventoryClient.class);
    }

    @Test
    void callsInventoryAcrossRealHttp() {
        assertEquals(new InventoryController.Stock("BOOK-1", 7),
                client().stock("BOOK-1"));
    }

    @Test
    void reportsMissingSkuAsHttp404() {
        assertThrows(HttpClientErrorException.NotFound.class,
                () -> client().stock("MISSING"));
    }
}

The first test receives a decoded Stock record. The second receives a client exception for the server’s 404 response. This differs from an RMI method contract: the HTTP response status is observable and should be mapped intentionally by the caller.

6. Plan the Remaining Migration Work

The code above demonstrates the call path; a production migration also needs contract decisions. Define stable URLs and JSON fields, including how missing resources and validation failures map to HTTP statuses. Configure connection and response timeouts so an unavailable inventory server does not stall checkout indefinitely. Apply authentication and authorization to the HTTP endpoint, and decide whether retries are safe for each operation.

An old client cannot keep using its RMI or Hessian protocol against the new controller. Migrate clients deliberately, or run a temporary adapter while consumers are upgraded. Avoid assuming that serialized Java objects can be reused as JSON without a contract review.

The Spring Framework HTTP service client reference covers client proxies and service groups. The Spring Framework 5.3 integration reference records the deprecation boundary for the legacy exporters.

7. Frequently Asked Questions

7.1. Was Java RMI removed from Java?

No. This migration concerns Spring Framework’s RMI exporter and proxy support. Java’s own RMI APIs are separate.

7.2. Is an HTTP service interface compatible with an existing Hessian client?

No. The interface proxy sends HTTP requests to a JSON endpoint. Hessian’s binary request and response format is different, so both ends of the integration need a migration plan.

7.3. Do the server and client need the same Java interface JAR?

They need to agree on the HTTP path, method and JSON representation. They can share a contract module if useful, but HTTP does not require sharing a Java service implementation or serialized object graph.

8. Conclusion

Spring Boot 4 applications can replace legacy Spring RMI/Hessian integration with an HTTP endpoint and a typed HTTP service client. The tested inventory example shows a successful remote read and an HTTP 404 across a real server connection. Migrating existing clients still requires an explicit protocol and data-contract change.

The complete Spring Boot remoting migration example contains the Maven project and integration tests.

Leave a Comment

  1. Hi, thanks for the useful article.
    I am using spring, and I followed along with your steps, but my client should obtain some DAO objects from the server in order to use them later in a web service controller.
    I am using three Tiers architecture.
    my first tier is DAO and I am exporting the dao object using RMI and the second tier is the web service.
    can you please guide me through the steps
    thanks in advance

Comments are closed.

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.