Skip to main content

Java SDK

The official Java SDK for Rexec provides an enterprise-ready interface for Terminal as a Service.

Requirements

  • Java 17 or later
  • Maven or Gradle

Installation

<dependency>
    <groupId>io.pipeops</groupId>
    <artifactId>rexec</artifactId>
    <version>1.0.0</version>
</dependency>

Quick Start

import io.pipeops.rexec.*;

public class Example {
    public static void main(String[] args) throws RexecException {
        // Create client
        RexecClient client = new RexecClient(
            "https://your-instance.com",
            "your-api-token"
        );

        // Create a container
        Container container = client.containers().create("ubuntu:24.04");
        System.out.println("Created: " + container.getId());

        // Start it
        client.containers().start(container.getId());

        // Execute a command
        ExecResult result = client.containers().exec(
            container.getId(),
            "echo 'Hello from Java!'"
        );
        System.out.println(result.getStdout());

        // Clean up
        client.containers().delete(container.getId());
    }
}

Client Initialization

Basic Client

import io.pipeops.rexec.RexecClient;

RexecClient client = new RexecClient(
    "https://your-instance.com",
    "your-api-token"
);

With Custom Timeout

import io.pipeops.rexec.RexecClient;
import io.pipeops.rexec.RexecConfig;

RexecConfig config = RexecConfig.builder()
    .baseUrl("https://your-instance.com")
    .token("your-api-token")
    .timeout(60) // seconds
    .build();

RexecClient client = new RexecClient(config);

Try-with-Resources

try (RexecClient client = new RexecClient(baseUrl, token)) {
    // Use client - automatically closed
}

Container Operations

The Container service provides methods for managing sandboxed environments.

List Containers

List<Container> containers = client.containers().list();

for (Container c : containers) {
    System.out.println(c.getName() + ": " + c.getStatus());
}

Get Container

Container container = client.containers().get(containerId);
System.out.println("Container " + container.getName() + " is " + container.getStatus());

Create Container

// Simple creation
Container container = client.containers().create("ubuntu:24.04");

// With options using builder pattern
Container container = client.containers().create(
    new CreateContainerRequest("python:3.12")
        .setName("my-python-sandbox")
        .addEnv("PYTHONPATH", "/app")
        .addEnv("DEBUG", "true")
        .addLabel("project", "demo")
);

System.out.println("Created: " + container.getId());

Start Container

client.containers().start(containerId);

Stop Container

client.containers().stop(containerId);

Delete Container

client.containers().delete(containerId);

Execute Commands

ExecResult result = client.containers().exec(
    containerId,
    "python --version"
);

if (result.isSuccess()) {
    System.out.println("Output: " + result.getStdout());
} else {
    System.err.println("Error: " + result.getStderr());
}

// Execute with array command
ExecResult result = client.containers().exec(
    containerId,
    new String[]{"python", "-c", "print('Hello')"}
);

File Operations

Manage files and directories within containers.

List Files

FileService files = client.files();

List<FileInfo> entries = files.list(containerId, "/app");
for (FileInfo file : entries) {
    String type = file.isDirectory() ?
        "DIR" : file.getSize() + " bytes";
    System.out.println(file.getName() + " - " + type);
}

Read File

String content = files.readString(containerId, "/etc/hostname");
System.out.println("Hostname: " + content);

// Or read as bytes
byte[] bytes = files.read(containerId, "/path/to/file");

Write File

files.write(
    containerId,
    "/app/script.py",
    "print('Hello!')"
);

// Or write bytes
files.write(containerId, "/path/to/file", byteArray);

Delete File

files.delete(containerId, "/tmp/scratch.txt");

Interactive Terminal

Connect to containers via WebSocket for real-time terminal access.

Basic Terminal Usage

Terminal terminal = client.terminal().connect(containerId);

// Set up handlers
terminal.onData(data -> System.out.print(data))
        .onClose(() -> System.out.println("Disconnected"))
        .onError(e -> e.printStackTrace());

// Send commands
terminal.write("ls -la\n");
terminal.write("cd /app && python main.py\n");

// Resize terminal
terminal.resize(120, 40);

// Clean up
terminal.close();

Terminal with Custom Size

Terminal terminal = client.terminal().connect(
    containerId,
    120, // cols
    40   // rows
);

Advanced Examples

Run Script and Capture Output

public String runScript(
    RexecClient client,
    String containerId,
    String script
) throws RexecException {
    StringBuilder output = new StringBuilder();
    CountDownLatch latch = new CountDownLatch(1);
    
    Terminal terminal = client.terminal().connect(containerId);
    
    terminal.onData(data -> output.append(data))
            .onClose(() -> latch.countDown());
    
    terminal.write(script + "\nexit\n");
    
    try {
        latch.await(30, TimeUnit.SECONDS);
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
    
    return output.toString();
}

// Usage
String result = runScript(
    client,
    container.getId(),
    "apt update && apt install -y curl"
);
System.out.println(result);

Parallel Container Creation

import java.util.concurrent.*;
import java.util.stream.*;

public List<Container> createBatch(
    RexecClient client,
    int count
) throws InterruptedException, ExecutionException {
    ExecutorService executor = Executors.newFixedThreadPool(5);
    
    List<CompletableFuture<Container>> futures = IntStream.range(0, count)
        .mapToObj(i -> CompletableFuture.supplyAsync(() -> {
            try {
                return client.containers().create(
                    new CreateContainerRequest("ubuntu:24.04")
                        .setName("worker-" + i)
                );
            } catch (RexecException e) {
                throw new RuntimeException(e);
            }
        }, executor))
        .collect(Collectors.toList());
    
    List<Container> containers = futures.stream()
        .map(CompletableFuture::join)
        .collect(Collectors.toList());
    
    executor.shutdown();
    return containers;
}

// Create 5 containers in parallel
List<Container> containers = createBatch(client, 5);
System.out.println("Created " + containers.size() + " containers");

File Upload

import java.nio.file.*;

public void uploadFile(
    RexecClient client,
    String containerId,
    Path localPath,
    String remotePath
) throws IOException, RexecException {
    byte[] content = Files.readAllBytes(localPath);
    client.files().write(containerId, remotePath, content);
}

// Usage
uploadFile(
    client,
    container.getId(),
    Paths.get("./local-script.sh"),
    "/home/script.sh"
);

Directory Sync

import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;

public void syncDirectory(
    RexecClient client,
    String containerId,
    Path localDir,
    String remoteDir
) throws IOException, RexecException {
    client.files().mkdir(containerId, remoteDir);
    
    Files.walkFileTree(localDir, new SimpleFileVisitor<Path>() {
        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
                throws IOException {
            try {
                Path relativePath = localDir.relativize(file);
                String remotePath = remoteDir + "/" +
                    relativePath.toString().replace("\\", "/");
                
                byte[] content = Files.readAllBytes(file);
                client.files().write(containerId, remotePath, content);
                
                System.out.println("Uploaded: " + remotePath);
            } catch (RexecException e) {
                throw new IOException(e);
            }
            return FileVisitResult.CONTINUE;
        }
    });
}

Real-time Log Streaming

public void streamLogs(
    RexecClient client,
    String containerId,
    String command
) throws RexecException {
    Terminal terminal = client.terminal().connect(containerId);
    
    terminal.onData(data -> {
        System.out.print(data);
        
        // Could also save to file
        // try (FileWriter fw = new FileWriter("logs.txt", true)) {
        //     fw.write(data);
        // }
    });
    
    terminal.onClose(() -> System.out.println("\nStream ended"));
    
    terminal.write(command + "\n");
    
    // Keep connection open
    try {
        Thread.sleep(Long.MAX_VALUE);
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
}

// Usage
streamLogs(client, container.getId(), "tail -f /var/log/app.log");

Error Handling

import io.pipeops.rexec.RexecException;

try {
    Container container = client.containers().get("invalid-id");
} catch (RexecException e) {
    if (e.isApiError()) {
        System.err.println("API error " + e.getStatusCode() +
            ": " + e.getMessage());
    } else {
        System.err.println("Network error: " + e.getMessage());
    }
}

Stream API Integration

Use Java Streams with SDK results:
import java.util.stream.Collectors;

// Filter containers
List<Container> running = client.containers().list()
    .stream()
    .filter(c -> "running".equals(c.getStatus()))
    .collect(Collectors.toList());

// Count by image
Map<String, Long> countByImage = client.containers().list()
    .stream()
    .collect(Collectors.groupingBy(
        Container::getImage,
        Collectors.counting()
    ));

// Get container names
List<String> names = client.containers().list()
    .stream()
    .map(Container::getName)
    .sorted()
    .collect(Collectors.toList());

Builder Pattern

The SDK uses builder patterns for complex configurations:
CreateContainerRequest request = new CreateContainerRequest("ubuntu:24.04")
    .setName("my-container")
    .addEnv("VAR1", "value1")
    .addEnv("VAR2", "value2")
    .addLabel("team", "backend")
    .addLabel("env", "production");

Container container = client.containers().create(request);

Thread Safety

The Java SDK is thread-safe for concurrent operations:
import java.util.concurrent.*;

ExecutorService executor = Executors.newFixedThreadPool(10);

for (int i = 0; i < 10; i++) {
    final int index = i;
    executor.submit(() -> {
        try {
            Container container = client.containers().create(
                new CreateContainerRequest("ubuntu:24.04")
                    .setName("worker-" + index)
            );
            System.out.println("Created: " + container.getId());
        } catch (RexecException e) {
            e.printStackTrace();
        }
    });
}

executor.shutdown();
executor.awaitTermination(1, TimeUnit.MINUTES);

Source Code

View the full source code on GitHub:

License

MIT License - see LICENSE for details.

Build docs developers (and LLMs) love