Official SDK

Java

Java 11+ with CompletableFuture async support. Designed for enterprise applications with Spring Boot integration and automatic retries.

Java 11+MIT licensed

Rename PDFs, split multi-page documents, and extract structured data using the official Java SDK for Renamed.to.

  1. 1Add to.renamed:renamed-sdk via Maven or Gradle and build the client
  2. 2Call rename(), pdfSplit(), or extract() with byte arrays or use async variants
  3. 3Integrate with Spring Boot or use CompletableFuture for non-blocking operations

MIT licensed, open source on GitHub, published on Maven Central.

Installation

Shell
<dependency>  <groupId>to.renamed</groupId>  <artifactId>renamed-sdk</artifactId>  <version>0.1.0</version></dependency>

Quickstart

Initialize the client and start making API calls.

Main.java
Java
import to.renamed.RenamedClient;import to.renamed.model.*;import java.nio.file.Files;import java.nio.file.Path;public class Main {    public static void main(String[] args) throws Exception {        // Initialize the client        var client = RenamedClient.builder()            .apiKey(System.getenv("RENAMED_API_KEY"))            .build();        // Rename a PDF        byte[] pdfBytes = Files.readAllBytes(Path.of("invoice.pdf"));        RenameResult result = client.rename(pdfBytes);        System.out.println(result.getSuggestedFilename());        // -> "2024-01-15_AcmeCorp_INV-1234.pdf"        // Split a multi-page PDF        byte[] docBytes = Files.readAllBytes(Path.of("documents.pdf"));        SplitJob job = client.pdfSplit(docBytes, SplitOptions.builder()            .mode("smart")            .build());        System.out.println(job.getDocuments());        // Extract structured data        byte[] receiptBytes = Files.readAllBytes(Path.of("receipt.pdf"));        var data = client.extract(receiptBytes);        System.out.println(data);    }}

Rename a PDF

Pass a byte array to get an AI-generated filename. Use custom instructions to control the naming format.

RenameExample.java
Java
import to.renamed.RenamedClient;import to.renamed.model.RenameOptions;import to.renamed.model.RenameResult;import to.renamed.exception.RenamedApiException;import java.nio.file.Files;import java.nio.file.Path;public class RenameExample {    public static void main(String[] args) {        // Initialize the client        var client = RenamedClient.builder()            .apiKey(System.getenv("RENAMED_API_KEY"))            .build();        try {            // Read the PDF file            byte[] pdfBytes = Files.readAllBytes(                Path.of("./documents/invoice.pdf")            );            // Rename the PDF            RenameResult result = client.rename(pdfBytes);            System.out.println("Suggested filename: " + result.getSuggestedFilename());            System.out.println("Confidence: " + result.getConfidence());            // Output:            // Suggested filename: 2024-01-15_AcmeCorp_INV-1234.pdf            // Confidence: 0.95            // Access extracted metadata            var metadata = result.getMetadata();            System.out.println("Vendor: " + metadata.get("vendor"));            System.out.println("Date: " + metadata.get("date"));            System.out.println("Type: " + metadata.get("type"));            // Use custom naming instructions            RenameResult customResult = client.rename(pdfBytes, RenameOptions.builder()                .instructions("Format: YYYY-MM-DD_VendorName_Amount")                .build());            System.out.println(customResult.getSuggestedFilename());            // -> "2024-01-15_AcmeCorp_$1250.pdf"        } catch (RenamedApiException e) {            System.err.println("API Error: " + e.getCode() + " - " + e.getMessage());        } catch (Exception e) {            System.err.println("Error: " + e.getMessage());        }    }}

Split a PDF

Split multi-page PDFs into separate documents. The API uses async jobs for large files—poll for completion or use the built-in wait helper.

SplitExample.java
Java
import to.renamed.RenamedClient;import to.renamed.model.SplitOptions;import to.renamed.model.SplitJob;import to.renamed.model.SplitDocument;import to.renamed.exception.RenamedApiException;import java.nio.file.Files;import java.nio.file.Path;import java.time.Duration;import java.util.concurrent.TimeUnit;public class SplitExample {    public static void main(String[] args) {        var client = RenamedClient.builder()            .apiKey(System.getenv("RENAMED_API_KEY"))            .timeout(Duration.ofMinutes(5))            .build();        try {            // Read a multi-page PDF            byte[] pdfBytes = Files.readAllBytes(                Path.of("./documents/combined.pdf")            );            // Submit the split job with smart mode (AI detects document boundaries)            SplitJob job = client.pdfSplit(pdfBytes, SplitOptions.builder()                .mode("smart")                .build());            System.out.println("Job ID: " + job.getJobId());            System.out.println("Status: " + job.getState());            // Option 1: Wait for completion (built-in polling)            SplitJob completed = job.waitForCompletion();            // Option 2: Manual polling for progress updates            // while (job.getState().equals("processing")) {            //     System.out.println("Progress: " + job.getProgress() + "%");            //     TimeUnit.SECONDS.sleep(1);            //     job = client.getJob(job.getJobId());            // }            // Process the split documents            System.out.println("Split into " + completed.getDocuments().size() + " documents:");            for (int i = 0; i < completed.getDocuments().size(); i++) {                SplitDocument doc = completed.getDocuments().get(i);                System.out.printf("  [%d] %s (pages %s)%n",                    i + 1, doc.getFilename(), doc.getPages());                // Output:                // [1] invoice_1.pdf (pages [1, 2])                // [2] invoice_2.pdf (pages [3, 4])                // Download each split document                byte[] docBytes = client.downloadFile(doc.getDownloadUrl());                // Save to output directory                Path outputPath = Path.of("./output", doc.getFilename());                Files.createDirectories(outputPath.getParent());                Files.write(outputPath, docBytes);            }        } catch (RenamedApiException e) {            System.err.println("API Error: " + e.getCode() + " - " + e.getMessage());        } catch (Exception e) {            System.err.println("Error: " + e.getMessage());        }    }}

Extract Data

Extract structured data from invoices, receipts, and other documents. Define Java records for type-safe extraction.

ExtractExample.java
Java
import to.renamed.RenamedClient;import to.renamed.model.ExtractOptions;import to.renamed.exception.RenamedApiException;import com.fasterxml.jackson.databind.ObjectMapper;import java.nio.file.Files;import java.nio.file.Path;import java.util.List;import java.util.Map;public class ExtractExample {    // Define typed records for invoice extraction    public record LineItem(        String description,        int quantity,        double unitPrice    ) {}    public record Invoice(        String invoiceNumber,        String vendor,        String date,        List<LineItem> lineItems,        double subtotal,        double tax,        double total    ) {}    public static void main(String[] args) {        var client = RenamedClient.builder()            .apiKey(System.getenv("RENAMED_API_KEY"))            .build();        var objectMapper = new ObjectMapper();        try {            // Read the PDF file            byte[] pdfBytes = Files.readAllBytes(                Path.of("./documents/invoice.pdf")            );            // Basic extraction (returns Map<String, Object>)            Map<String, Object> data = client.extract(pdfBytes);            System.out.println("Vendor: " + data.get("vendor"));            System.out.println("Date: " + data.get("date"));            System.out.println("Total: " + data.get("total"));            // Output:            // Vendor: Acme Corp            // Date: 2024-01-15            // Total: 1250.00            // Typed extraction: convert to a record            Invoice invoice = objectMapper.convertValue(data, Invoice.class);            // Now you have fully typed access            System.out.printf("Invoice #%s from %s%n",                invoice.invoiceNumber(), invoice.vendor());            System.out.println("Line items: " + invoice.lineItems().size());            for (LineItem item : invoice.lineItems()) {                System.out.printf("  - %s: %d x $%.2f%n",                    item.description(), item.quantity(), item.unitPrice());            }            System.out.printf("Total: $%.2f%n", invoice.total());            // Output:            // Invoice #INV-2024-1234 from Acme Corp            // Line items: 2            //   - Widget Pro: 5 x $199.00            //   - Support Plan: 1 x $342.50            // Total: $1337.50        } catch (RenamedApiException e) {            System.err.println("API Error: " + e.getCode() + " - " + e.getMessage());        } catch (Exception e) {            System.err.println("Error: " + e.getMessage());        }    }}

Framework Integrations

Common integration patterns for popular frameworks.

Spring Boot RestController

Handle PDF uploads in a Spring Boot application with proper validation and error handling.

DocumentController.java
Java
package com.example.demo.controller;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.*;import org.springframework.web.multipart.MultipartFile;import to.renamed.RenamedClient;import to.renamed.model.RenameResult;import to.renamed.model.SplitJob;import to.renamed.model.SplitOptions;import to.renamed.exception.RenamedApiException;import java.io.IOException;import java.util.List;import java.util.Map;@RestController@RequestMapping("/api/documents")public class DocumentController {    private final RenamedClient renamedClient;    public DocumentController(RenamedClient renamedClient) {        this.renamedClient = renamedClient;    }    @PostMapping("/rename")    public ResponseEntity<?> renameDocument(@RequestParam("file") MultipartFile file) {        // Validate file type        if (file.isEmpty()) {            return ResponseEntity.badRequest()                .body(Map.of("error", "No file provided"));        }        String contentType = file.getContentType();        if (contentType == null || !contentType.equals("application/pdf")) {            return ResponseEntity.badRequest()                .body(Map.of("error", "Only PDF files are supported"));        }        try {            byte[] pdfBytes = file.getBytes();            RenameResult result = renamedClient.rename(pdfBytes);            return ResponseEntity.ok(Map.of(                "suggestedFilename", result.getSuggestedFilename(),                "confidence", result.getConfidence(),                "metadata", result.getMetadata()            ));        } catch (RenamedApiException e) {            return ResponseEntity.status(502)                .body(Map.of("error", e.getMessage(), "code", e.getCode()));        } catch (IOException e) {            return ResponseEntity.status(500)                .body(Map.of("error", "Failed to read file"));        }    }    @PostMapping("/split")    public ResponseEntity<?> splitDocument(            @RequestParam("file") MultipartFile file,            @RequestParam(value = "mode", defaultValue = "smart") String mode) {        if (file.isEmpty()) {            return ResponseEntity.badRequest()                .body(Map.of("error", "No file provided"));        }        try {            byte[] pdfBytes = file.getBytes();            SplitJob job = renamedClient.pdfSplit(pdfBytes, SplitOptions.builder()                .mode(mode)                .build());            // Wait for completion            SplitJob completed = job.waitForCompletion();            return ResponseEntity.ok(Map.of(                "jobId", completed.getJobId(),                "documents", completed.getDocuments().stream()                    .map(doc -> Map.of(                        "filename", doc.getFilename(),                        "pages", doc.getPages(),                        "downloadUrl", doc.getDownloadUrl()                    ))                    .toList()            ));        } catch (RenamedApiException e) {            return ResponseEntity.status(502)                .body(Map.of("error", e.getMessage()));        } catch (IOException e) {            return ResponseEntity.status(500)                .body(Map.of("error", "Failed to read file"));        }    }    @PostMapping("/extract")    public ResponseEntity<?> extractData(@RequestParam("file") MultipartFile file) {        if (file.isEmpty()) {            return ResponseEntity.badRequest()                .body(Map.of("error", "No file provided"));        }        try {            byte[] pdfBytes = file.getBytes();            Map<String, Object> data = renamedClient.extract(pdfBytes);            return ResponseEntity.ok(data);        } catch (RenamedApiException e) {            return ResponseEntity.status(502)                .body(Map.of("error", e.getMessage()));        } catch (IOException e) {            return ResponseEntity.status(500)                .body(Map.of("error", "Failed to read file"));        }    }}

Spring Boot Configuration

Configure the Renamed client as a Spring Bean with properties-based configuration.

RenamedConfig.java
Java
package com.example.demo.config;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import to.renamed.RenamedClient;import java.time.Duration;@Configurationpublic class RenamedConfig {    @Bean    public RenamedClient renamedClient(            @Value("${renamed.api-key}") String apiKey,            @Value("${renamed.timeout:30}") int timeoutSeconds,            @Value("${renamed.max-retries:3}") int maxRetries) {        return RenamedClient.builder()            .apiKey(apiKey)            .timeout(Duration.ofSeconds(timeoutSeconds))            .maxRetries(maxRetries)            .build();    }}// application.properties or application.yml:// renamed.api-key=${RENAMED_API_KEY}// renamed.timeout=30// renamed.max-retries=3

Async with CompletableFuture

Use CompletableFuture for non-blocking operations and parallel processing.

AsyncExample.java
Java
import to.renamed.RenamedClient;import to.renamed.model.RenameResult;import to.renamed.model.SplitJob;import to.renamed.model.SplitOptions;import java.nio.file.Files;import java.nio.file.Path;import java.util.List;import java.util.concurrent.CompletableFuture;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class AsyncExample {    public static void main(String[] args) throws Exception {        var client = RenamedClient.builder()            .apiKey(System.getenv("RENAMED_API_KEY"))            .build();        // Single async operation        byte[] pdfBytes = Files.readAllBytes(Path.of("invoice.pdf"));        CompletableFuture<RenameResult> future = client.renameAsync(pdfBytes);        future            .thenAccept(result -> {                System.out.println("Renamed to: " + result.getSuggestedFilename());                System.out.println("Confidence: " + result.getConfidence());            })            .exceptionally(e -> {                System.err.println("Error: " + e.getMessage());                return null;            });        // Process multiple files in parallel        List<Path> files = List.of(            Path.of("doc1.pdf"),            Path.of("doc2.pdf"),            Path.of("doc3.pdf")        );        List<CompletableFuture<RenameResult>> futures = files.stream()            .map(path -> {                try {                    byte[] bytes = Files.readAllBytes(path);                    return client.renameAsync(bytes);                } catch (Exception e) {                    return CompletableFuture.<RenameResult>failedFuture(e);                }            })            .toList();        // Wait for all to complete        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))            .thenRun(() -> {                futures.forEach(f -> {                    try {                        RenameResult result = f.join();                        System.out.println("Result: " + result.getSuggestedFilename());                    } catch (Exception e) {                        System.err.println("Failed: " + e.getMessage());                    }                });            })            .join();        // Async PDF split with progress monitoring        byte[] combinedPdf = Files.readAllBytes(Path.of("combined.pdf"));        client.pdfSplitAsync(combinedPdf, SplitOptions.builder().mode("smart").build())            .thenCompose(job -> {                System.out.println("Job started: " + job.getJobId());                return job.waitForCompletionAsync();            })            .thenAccept(completed -> {                System.out.println("Split into " + completed.getDocuments().size() + " documents");                completed.getDocuments().forEach(doc ->                    System.out.println("  - " + doc.getFilename())                );            })            .exceptionally(e -> {                System.err.println("Split failed: " + e.getMessage());                return null;            })            .join();    }}

Error Handling

The SDK throws typed exceptions that you can catch and handle appropriately. Use try-catch for synchronous calls or handle CompletableFuture exceptions for async.

ErrorHandling.java
Java
import to.renamed.RenamedClient;import to.renamed.model.RenameResult;import to.renamed.exception.RateLimitException;import to.renamed.exception.RenamedApiException;import to.renamed.exception.InvalidFileException;import to.renamed.exception.AuthenticationException;import java.nio.file.Files;import java.nio.file.Path;import java.util.concurrent.CompletableFuture;import java.util.concurrent.TimeUnit;public class ErrorHandling {    public static void main(String[] args) {        var client = RenamedClient.builder()            .apiKey(System.getenv("RENAMED_API_KEY"))            .build();        try {            byte[] pdfBytes = Files.readAllBytes(Path.of("invoice.pdf"));            RenameResult result = client.rename(pdfBytes);            System.out.println("Renamed to: " + result.getSuggestedFilename());        } catch (RateLimitException e) {            // Rate limited - wait and retry            System.out.printf("Rate limited. Retry after %d seconds%n", e.getRetryAfter());            try {                TimeUnit.SECONDS.sleep(e.getRetryAfter());                // Retry the request...            } catch (InterruptedException ie) {                Thread.currentThread().interrupt();            }        } catch (InvalidFileException e) {            // File is not a valid PDF or is corrupted            System.out.println("Invalid file: " + e.getMessage());        } catch (AuthenticationException e) {            // API key is invalid or expired            System.out.println("Authentication failed: " + e.getMessage());        } catch (RenamedApiException e) {            // Other API errors            System.out.printf("API Error [%s]: %s%n", e.getCode(), e.getMessage());            if (e.getDetails() != null) {                System.out.println("Details: " + e.getDetails());            }        } catch (Exception e) {            // Network errors, file I/O errors, etc.            System.out.println("Unexpected error: " + e.getMessage());        }        // Async error handling with CompletableFuture        CompletableFuture<RenameResult> future = client.renameAsync(new byte[0]);        future.handle((result, throwable) -> {            if (throwable != null) {                Throwable cause = throwable.getCause();                if (cause instanceof RateLimitException rle) {                    System.out.println("Async rate limited: " + rle.getRetryAfter());                } else if (cause instanceof RenamedApiException rae) {                    System.out.println("Async API error: " + rae.getMessage());                } else {                    System.out.println("Async error: " + throwable.getMessage());                }                return null;            }            System.out.println("Async success: " + result.getSuggestedFilename());            return result;        });    }}

Full documentation on GitHub

For more examples, advanced usage patterns, and detailed API documentation, see the full Java SDK README on GitHub.

Read the Java SDK docs

Frequently asked questions

What Java versions are supported?
The Java SDK supports Java 11 and above. It uses modern Java features like records and CompletableFuture for async operations.
Does it work with Spring Boot?
Yes, the SDK integrates seamlessly with Spring Boot. You can configure it as a Bean and inject it where needed.
Which build tools are supported?
The SDK is available on Maven Central and works with Maven, Gradle, and any other build tool that supports Maven repositories.
How do I handle async operations?
All methods have async variants that return CompletableFuture. Use thenAccept(), thenCompose(), or handle() for chaining operations.
Is the client thread-safe?
Yes, the RenamedClient is thread-safe and can be shared across threads. Use a single instance per application.

Related resources

Other languages