A stack of scanned pages is rarely a single document. Mailroom batches, loan packages, and archived files arrive as one merged PDF that actually holds an invoice, then a contract, then an ID card — concatenated in order. Before you can route, index, or extract anything, you have to find where each sub-document starts. That’s page-stream segmentation, or document splitting.
This sample shows how to split a merged document into its constituent sub-documents using Nutrient Java SDK. Splitting predicts, for every page, whether it starts a new sub-document, and returns the resulting page ranges. It runs locally.
Download sampleHow Nutrient helps
Nutrient Java SDK runs the full page-stream segmentation pipeline behind a single method call. The SDK handles:
- Rendering every page to a bitmap at the resolution the image model expects
- Deriving each page’s text from the extraction pipeline
- Scoring each page for whether it begins a new sub-document, fusing the visual and text signals
- Grouping consecutive pages into sub-document page ranges
- Serializing the result to JSON
The result is the list of detected sub-documents — each a contiguous, 1-based, inclusive page range with the confidence that a new document starts there.
Why two signals
A new document often announces itself visually — a fresh letterhead, a different layout, a form’s first page — and just as often in its text — a new salutation, a heading, an invoice number. Scoring both the page image and the page’s text and fusing them is more robust than either alone, especially for scanned batches where layout and wording shift together at a boundary.
The boundary threshold
Each page gets a calibrated boundary confidence between 0 and 1. A page starts a new sub-document when its confidence meets boundaryThreshold (the first page is always a boundary). Raise the threshold for fewer, larger segments when the model must be more certain to cut; lower it to split more eagerly. The default (0.5) is a balanced starting point.
Document splitting requires the document split feature in your license.
Complete implementation
Declare the sample’s package:
package io.nutrient.Sample;Import the classes used in the sample:
import io.nutrient.sdk.Document;import io.nutrient.sdk.Vision;import io.nutrient.sdk.exceptions.NutrientException;
import java.io.IOException;import java.nio.file.Files;import java.nio.file.Path;Loading the document
Open the merged document in a try-with-resources block so resources are cleaned up after processing:
public class SplitDocument { public static void main(String[] args) { try (Document document = Document.open("merged_documents.pdf")) {Configuring the split
Tune the behavior on the document’s split settings. This example keeps the default threshold and asks for the per-page confidences so you can inspect the decision:
var split = document.getSettings().getDocumentSplitSettings();
// A page starts a new sub-document when its calibrated confidence meets this (0..1). // Higher = fewer, larger segments. Default 0.5. split.setBoundaryThreshold(0.5f);
// Diagnostic: also return the calibrated boundary confidence for every page. split.setIncludePageConfidences(true);Splitting the document
Create a vision instance bound to the document with Vision.set(document), then call split():
Vision vision = Vision.set(document); String resultJson = vision.split();
Files.writeString(Path.of("output.json"), resultJson); } catch (NutrientException | IOException e) { System.err.println("Error: " + e.getMessage()); } }}Understanding the output
split() returns JSON with a top-level split object:
segments— The detected sub-documents, in reading order. Each entry carries:startPage— First page of the sub-document (1-based, inclusive).endPage— Last page of the sub-document (1-based, inclusive).pageCount— Number of pages in the sub-document.boundaryConfidence— Calibrated confidence (0.0 to 1.0) that a new sub-document starts atstartPage. The first segment (page 1) is a forced boundary and reports 1.
pageConfidences— When you enableincludePageConfidences, the calibrated boundary confidence for every page in order (index 0 is page 1), so you can re-threshold offline without re-running the model.
To turn the segments into separate files, use the page ranges with your PDF page-extraction workflow — each startPage–endPage range is one sub-document.
Choosing the threshold
If the splitter is over-segmenting (cutting a single document into pieces), raise boundaryThreshold toward 1. If it’s merging distinct documents, lower it toward 0. Turning on includePageConfidences shows exactly how close each page was to the cut, which makes tuning the threshold straightforward.
Error handling
Vision API throws VisionException (a NutrientException) when splitting fails.
Common failure scenarios include:
- The document can’t be read due to path or permission issues
- A page produces no renderable image
- A splitting model is missing or inaccessible, or the feature isn’t licensed
- The document exceeds the splitter’s maximum page count (split the stream into sub-ranges and process each)
In production code:
- Catch
NutrientException. - Return a clear error message.
- Log failure details for debugging.
Conclusion
The workflow for document splitting is:
- Open the merged document using try-with-resources for automatic resource cleanup.
- Tune
boundaryThreshold(and optionallyincludePageConfidences) on the split settings. - Create a vision instance with
Vision.set(). - Call
split()to segment the document and export the page ranges as JSON. - Write the JSON to a file, then extract each
startPage–endPagerange as its own document. - Handle
NutrientExceptionfor robust error recovery.
For related document workflows, refer to the Java SDK guides.
Download this ready-to-use sample package to explore document splitting.