A production-ready Java 21 Remote Procedure Call framework with factory-based instance creation and connection pooling. Built on Virtual Threads for high-scalability distributed computing.
- ✅ Factory-Based Instances: Create remote instances like
new Foo()- no string IDs - ✅ Connection Pooling: Efficient connection reuse with configurable pool sizes
- ✅ Project Loom: Virtual Threads handle thousands of concurrent calls
- ✅ Generic Proxying: Dynamically proxy any Java interface at runtime
- ✅ Type-Safe: Full compile-time type safety with generics
- ✅ Keep-Alive: Persistent connections for reduced latency
- ✅ Constructor Arguments: Support for parameterized constructors
- ✅ Multi-Format Serialization: Choose between JSON, XML, YAML, or MessagePack with automatic type coercion
- ✅ Interface Validation: Only methods declared in service interfaces can be invoked
- ✅ Secure Serialization: Jackson-based serialization (no Java serialization vulnerabilities)
- ✅ Error Handling: Comprehensive exception propagation with stack traces
- ✅ Logging: SLF4J + Logback for production observability
- ✅ Thread-Safe: Concurrent access supported via ConcurrentHashMap and BlockingQueue
- ✅ Instance Ownership: Clients can only destroy their own instances
- ✅ Intuitive API: Works like
new Foo()instead of string-based lookup - ✅ Auto-Closeable: try-with-resources support with automatic cleanup
- ✅ Comprehensive Tests: 181 tests covering all features and edge cases
- ✅ CI/CD Ready: Automated versioning and deployment
- ✅ Cross-Platform: Windows batch/PowerShell scripts alongside Unix/Linux support
<repository>
<id>packagecloud-flossware</id>
<url>https://packagecloud.io/flossware/java/maven2/</url>
</repository>
<dependency>
<groupId>org.flossware</groupId>
<artifactId>jremote</artifactId>
<version>1.0</version>
</dependency>public interface UserService {
String createUser(String name);
User getUser(int id);
void deleteUser(int id);
}public class UserServiceImpl implements UserService {
@Override
public String createUser(String name) {
return "User created: " + name;
}
@Override
public User getUser(int id) {
return new User(id, "Alice");
}
@Override
public void deleteUser(int id) {
// Implementation
}
}import org.flossware.jremote.JRemoteServer;
public class Server {
public static void main(String[] args) {
JRemoteServer server = JRemoteServer.builder()
.registerFactory(UserService.class, UserServiceImpl.class)
.registerFactory(OrderService.class, OrderServiceImpl.class)
.build();
server.start(8080);
System.out.println("Server running on port 8080");
}
}import org.flossware.jremote.JRemoteClient;
public class Client {
public static void main(String[] args) {
try (JRemoteClient client = new JRemoteClient("localhost", 8080)) {
// Create remote instances like "new Foo()"
UserService user1 = client.create(UserService.class);
UserService user2 = client.create(UserService.class); // Different instance
// Use them
String result = user1.createUser("Alice");
System.out.println(result);
User user = user2.getUser(1);
System.out.println("Retrieved: " + user.getName());
// Explicit cleanup (optional)
client.destroy(user1);
} // user2 auto-destroyed on close
}
}// Server: register factory
JRemoteServer server = JRemoteServer.builder()
.registerFactory(DatabaseService.class, DatabaseServiceImpl.class)
.build();
// Client: create with constructor arguments
try (JRemoteClient client = new JRemoteClient("localhost", 8080)) {
DatabaseService db = client.create(
DatabaseService.class,
"jdbc:postgresql://localhost:5432/mydb", // connection string
"username", // username
"password" // password
);
db.query("SELECT * FROM users");
}For complex initialization logic, use a Supplier:
JRemoteServer server = JRemoteServer.builder()
.registerFactory(OrderService.class, () -> {
OrderServiceImpl service = new OrderServiceImpl();
service.setDatabase(dbConnection);
service.setLogger(logger);
return service;
})
.build();// Default pool (min=1, max=10)
JRemoteClient client = new JRemoteClient("localhost", 8080);
// Custom pool size
JRemoteClient client = new JRemoteClient("localhost", 8080, 5, 20);jremote supports multiple serialization formats. Clients can choose the format that best fits their needs:
import org.flossware.jremote.SerializationFormat;
// JSON (default - human-readable, widely supported)
JRemoteClient jsonClient = new JRemoteClient("localhost", 8080);
// or explicitly
JRemoteClient jsonClient = new JRemoteClient("localhost", 8080, SerializationFormat.JSON);
// XML (enterprise integration standard)
JRemoteClient xmlClient = new JRemoteClient("localhost", 8080, SerializationFormat.XML);
// YAML (human-readable, configuration-friendly)
JRemoteClient yamlClient = new JRemoteClient("localhost", 8080, SerializationFormat.YAML);
// MessagePack (compact binary format for performance)
JRemoteClient msgpackClient = new JRemoteClient("localhost", 8080, SerializationFormat.MESSAGEPACK);Format Selection:
- Server auto-detects format from each client message
- Multiple clients with different formats can connect simultaneously
- JSON is the default for backward compatibility
- Automatic type coercion handles format-specific serialization differences
- Each format has trade-offs:
- JSON: Best compatibility, human-readable, moderate performance
- XML: Enterprise standard, verbose but widely supported
- YAML: Most human-readable, good for debugging
- MessagePack: Most compact, best performance, not human-readable
Type Coercion: jremote automatically handles type conversions between serialization formats:
- String → primitive/wrapper (e.g., XML deserializes numbers as strings)
- Number wrapper → primitive (e.g., Integer → int for method parameters)
- Supports all primitive types: int, long, double, float, boolean, byte, short, char
- Applied to both method arguments (server-side) and return values (client-side)
Each create() call creates a new remote instance:
try (JRemoteClient client = new JRemoteClient("localhost", 8080)) {
UserService user1 = client.create(UserService.class);
UserService user2 = client.create(UserService.class);
UserService user3 = client.create(UserService.class);
// All three are independent instances
user1.createUser("Alice");
user2.createUser("Bob");
user3.createUser("Carol");
} // All three auto-destroyedtry (JRemoteClient client = new JRemoteClient("localhost", 8080)) {
UserService user = client.create(UserService.class);
// Use the service
user.createUser("Alice");
// Explicit cleanup (optional)
client.destroy(user);
// Create another instance
UserService user2 = client.create(UserService.class);
} // user2 auto-destroyed on closetry (JRemoteClient client = new JRemoteClient("localhost", 8080)) {
UserService users = client.create(UserService.class);
try {
users.deleteUser(999); // Might throw exception
} catch (RemoteException e) {
System.err.println("Remote call failed: " + e.getOriginalExceptionType());
System.err.println("Message: " + e.getOriginalMessage());
e.printStackTrace(); // Stack trace from remote server preserved
}
}Client Side Server Side
─────────── ───────────
JRemoteClient JRemoteServer
↓ ↓
create(Class) Factory Registry
↓ ↓
CREATE_INSTANCE request ────────────→ Create Instance
↓ ↓
Receive objectId ←────────────────────── Return objectId
↓ ↓
Create Dynamic Proxy Track Instance
↓ ↓
proxy.method() ──────────────────────→ Lookup Instance
↓ ↓
METHOD_CALL request Invoke Method
↓ ↓
Receive result ←───────────────────────── Return Result
↓ ↓
destroy(proxy) ───────────────────────→ Destroy Instance
CREATE_INSTANCE: Create new remote instance
- Client sends interface name + constructor arguments
- Server calls factory, generates UUID, returns objectId
- Client creates proxy with embedded objectId
METHOD_CALL: Invoke method on remote instance
- Client sends objectId + method name + arguments
- Server routes to instance, validates method, invokes
- Returns result or error
DESTROY_INSTANCE: Destroy remote instance
- Client sends objectId
- Server validates ownership, removes instance
- JRemoteClient: Client-side proxy factory with connection pooling
- JRemoteServer: Server-side request handler with factory registry
- ConnectionPool: Thread-safe socket connection pool
- ServiceRegistry: Registry for managing factories and instances
- InstanceFactory: Factory abstraction (reflection or custom Supplier)
- RemoteInvocation: JSON-serializable request wrapper
- RemoteResponse: JSON-serializable response wrapper
- RemoteException: Custom exception preserving remote stack traces
Uses SLF4J with Logback. Configure in logback.xml:
<configuration>
<logger name="org.flossware.jremote" level="DEBUG"/>
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
</configuration>- Only methods declared in the registered interface can be invoked
- Attempts to call non-interface methods are rejected with
SecurityException - JSON serialization prevents deserialization attacks
- Instance ownership prevents cross-client destruction
This project uses strict X.Y versioning (no X.Y.Z, no SNAPSHOTs):
- Major.Minor format enforced by Maven Enforcer plugin
- Automated version bumping via GitHub Actions
- Each release tagged in git
# Run tests
mvn test
# Build JAR
mvn clean install
# Validate version format
mvn validateCommand Prompt (Batch scripts):
test.bat :: Run tests
build.bat :: Clean and compile
package.bat :: Create JAR
install.bat :: Install to local Maven repo
clean.bat :: Clean build artifactsPowerShell:
.\test.ps1 # Run tests
.\build.ps1 # Clean and compile
.\package.ps1 # Create JAR
.\install.ps1 # Install to local Maven repo
.\clean.ps1 # Clean build artifactsSee SCRIPTS.md for detailed Windows build script documentation.
Comprehensive test suite with 181 tests (97 integration + 84 unit):
mvn testTest coverage includes:
Integration Tests (97):
- ✅ Factory-based instance creation
- ✅ Constructor arguments support
- ✅ Multi-format serialization (JSON, XML, YAML, MessagePack)
- ✅ Multiple instances of same interface
- ✅ Instance lifecycle (create/destroy/auto-cleanup)
- ✅ Connection pool validation
- ✅ Security validation (interface method whitelist)
- ✅ Ownership validation (cross-client protection)
- ✅ Exception propagation with stack traces
- ✅ Concurrent client access
- ✅ Keep-alive connection reuse
Unit Tests (84):
- ✅ All serialization strategies (JSON, XML, YAML, MessagePack)
- ✅ Type coercion (String/Number → primitives)
- ✅ Format marker detection and validation
- ✅ Serialization strategy factory singleton pattern
- ✅ Round-trip serialization for all request/response types
- ✅ Null handling and edge cases
- ✅ Binary vs text format validation
- Java 21+ (Virtual Threads, Records, Pattern Matching)
- Maven 3.8+
- Jackson 2.17.0 (JSON, XML, YAML serialization)
- MessagePack 0.9.8 (Binary serialization)
- SLF4J 2.0.12 (Logging API)
- Logback 1.5.3 (Logging implementation)
- JUnit 5.10.2 (Testing)
- Latency: Eliminates TCP handshake overhead (typically 1-3ms per call)
- Throughput: Supports high-concurrency workloads with virtual threads
- Resource Efficiency: Bounded connection pool prevents resource exhaustion
- Virtual threads allow thousands of concurrent remote calls
- Connection pool size configurable based on workload
- Keep-alive connections reduce per-call overhead
- Each client manages its own instances independently
Current limitations:
- No built-in load balancing (use external load balancer)
- No SSL/TLS support (use reverse proxy or VPN)
- No service discovery (clients must know server address)
- No heartbeat/ping protocol for connection validation
Planned enhancements:
- Dynamic factory registration/deregistration
- Service discovery mechanism
- Connection pool metrics and monitoring
- SSL/TLS native support
- Additional serialization formats (Protobuf, Avro)
- HTTP/REST transport option alongside TCP
- Fork the repository
- Create a feature branch
- Make changes with tests
- Ensure
mvn clean installpasses - Submit pull request
[Add license information]
Built with ❤️ by FlossWare using Java 21 Virtual Threads (Project Loom).
- GitHub: https://github.com/FlossWare/jremote
- Issues: https://github.com/FlossWare/jremote/issues
- Packagecloud: https://packagecloud.io/flossware/java