This repository demonstrates how to compare metadata between two versions of a document using GroupDocs.Metadata for Java. Whether you need to audit legal contracts or verify document provenance, these examples provide practical solutions for forensic metadata analysis.
If you need to detect hidden changes, track ownership modifications, or generate compliance reports from document revisions, this demo shows a concise way to extract, diff, and export metadata. It works with any Office Open XML file and produces CSV or JSON audit logs that can be ingested by SIEMs or data‑warehouses.
This repository addresses the following use cases:
- Legal e‑discovery: Identify who edited a contract and when.
- Compliance auditing: Generate immutable reports of metadata changes for regulatory review.
- Forensic investigations: Spot unauthorized ownership swaps between document versions.
- Version control integration: Sync metadata diffs with CI pipelines for automated checks.
In many applications, developers need to compare document metadata across revisions to prove authenticity. However, manually parsing XML structures or using generic file‑diff tools makes this difficult. Additionally, extracting time‑stamped properties such as LastPrinted or RevisionNumber requires deep knowledge of each file format, which can be time‑consuming to implement from scratch. GroupDocs.Metadata addresses these challenges by providing a unified API that abstracts format‑specific details.
- ❌ Parsing proprietary binary formats manually.
- ❌ Missing support for custom metadata fields.
- ❌ Inconsistent handling of timestamps across Office versions.
- ❌ Lack of ready‑to‑use export formats for audit logs.
GroupDocs.Metadata provides a comprehensive solution for metadata comparison across document versions. Key capabilities include:
✅ Unified property extraction – Retrieve every built‑in and custom tag with a single call. ✅ Change detection engine – Automatically classify added, removed, and modified properties. ✅ Ownership analysis – Isolate creator, editor, and company fields to spot tampering. ✅ Revision timeline tracking – Capture modification, creation, and print timestamps. ✅ Export utilities – Serialize diffs to CSV or JSON for downstream processing.
Here's a typical workflow for implementing compare-metadata-between-document-versions-java:
- Load documents: Open both versions with
Metadataobjects. - Extract full metadata: Use
ExtractAllMetadata.runto build name‑value maps. - Compute diff: Call
CompareMetadataSets.runto get added/removed/changed entries. - Analyze ownership: Run
DetectOwnershipChanges.runfor creator/editor changes. - Export report: Serialize the diff with
ExportDiffToCsv.runorExportDiffToJson.run.
To use these examples, you'll need:
- Java – JDK 8 or higher (the project targets Java 8).
- GroupDocs.Metadata – version 24.7 (released July 2024, see release notes).
- Maven – for dependency management and build execution.
compare-metadata-between-document-versions-java/
│
├── pom.xml
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ └── groupdocs
│ │ │ └── samples
│ │ │ └── comparemetadataversions
│ │ │ ├── Main.java
│ │ │ └── methods
│ │ │ ├── CompareMetadataSets.java
│ │ │ ├── DetectOwnershipChanges.java
│ │ │ ├── DetectRevisionHistory.java
│ │ │ ├── ExportDiffToCsv.java
│ │ │ ├── ExportDiffToJson.java
│ │ │ ├── ExtractAllMetadata.java
│ │ │ └── MetadataDiff.java
│ └── resources
│ ├── document-v1.docx
│ └── document-v2.docx
File Organization:
- pom.xml – Maven project descriptor with GroupDocs.Metadata dependency.
- Main.java – Entry point that orchestrates loading, diffing, and exporting.
- CompareMetadataSets.java – Computes a structured diff between two metadata maps.
- DetectOwnershipChanges.java – Highlights creator/editor/manager/company changes.
- DetectRevisionHistory.java – Extracts time‑based revision properties.
- ExportDiffToCsv.java – Writes the diff to a CSV audit file.
- ExportDiffToJson.java – Writes the diff to a JSON audit file.
- ExtractAllMetadata.java – Retrieves every accessible metadata property.
- MetadataDiff.java – Simple DTO holding added, removed, and changed entries.
When to Use: When you need a programmatic view of what metadata changed between two revisions.
The method loads both documents, extracts all properties, and builds a MetadataDiff object that separates added, removed, and changed entries.
Map<String, String> v1 = ExtractAllMetadata.run(pathV1);
Map<String, String> v2 = ExtractAllMetadata.run(pathV2);
MetadataDiff diff = new MetadataDiff();
for (Map.Entry<String, String> e : v2.entrySet()) {
String k = e.getKey();
String v = e.getValue();
if (!v1.containsKey(k)) {
diff.added.put(k, v);
} else if (!v1.get(k).equals(v)) {
diff.changed.put(k, new String[]{v1.get(k), v});
}
}
for (Map.Entry<String, String> e : v1.entrySet()) {
if (!v2.containsKey(e.getKey())) {
diff.removed.put(e.getKey(), e.getValue());
}
}
return diff;What This Solves: Provides a clear, programmatic diff of metadata without manual XML parsing.
Real-World Application: Used in legal audits to prove that a contract's author information was altered between versions.
When to Use: When you must verify that the creator, editor, or company fields remain consistent.
The method reads ownership‑related tags from both documents, merges key sets, and records any differences.
Map<String, String> v1 = readOwnership(pathV1);
Map<String, String> v2 = readOwnership(pathV2);
Set<String> keys = new HashSet<>(v1.keySet());
keys.addAll(v2.keySet());
Map<String, String[]> changes = new LinkedHashMap<>();
for (String k : keys) {
String oldV = v1.getOrDefault(k, "<missing>");
String newV = v2.getOrDefault(k, "<missing>");
if (!oldV.equals(newV)) {
changes.put(k, new String[]{oldV, newV});
}
}
return changes;What This Solves: Highlights unauthorized ownership swaps that could indicate tampering.
Real-World Application: Helps compliance teams flag documents where the listed creator changes unexpectedly.
When to Use: As a helper for the ownership‑change detector to obtain the raw values.
The method opens a document, searches for person‑related tags (creator, editor, manager) and corporate tags (company), and returns a map of found values.
Map<String, String> result = new LinkedHashMap<>();
try (Metadata metadata = new Metadata(path)) {
if (metadata.getFileFormat() == FileFormat.Unknown) return result;
for (MetadataProperty p : metadata.findProperties(
new ContainsTagSpecification(Tags.getPerson().getCreator())
.or(new ContainsTagSpecification(Tags.getPerson().getEditor()))
.or(new ContainsTagSpecification(Tags.getPerson().getManager()))
.or(new ContainsTagSpecification(Tags.getCorporate().getCompany())))) {
String value = "";
if (p.getValue() != null && p.getValue().getRawValue() != null) {
value = String.valueOf(p.getValue().getRawValue());
}
result.put(p.getName(), value);
}
}
return result;What This Solves: Centralises ownership extraction, reducing duplicated code.
Real-World Application: Used by auditors to pull creator/editor info from any Office document.
Use Case: Detects revision‑related metadata changes such as RevisionNumber, TotalEditingTime, and LastPrinted.
When to Use: When you need to track editing activity that is not visible in the document content.
The method reads time‑based tags from both versions and reports any differences.
Map<String, String> v1 = readRevision(pathV1);
Map<String, String> v2 = readRevision(pathV2);
Set<String> keys = new HashSet<>(v1.keySet());
keys.addAll(v2.keySet());
Map<String, String[]> changes = new LinkedHashMap<>();
for (String k : keys) {
String oldV = v1.getOrDefault(k, "<missing>");
String newV = v2.getOrDefault(k, "<missing>");
if (!oldV.equals(newV)) {
changes.put(k, new String[]{oldV, newV});
}
}
return changes;What This Solves: Exposes hidden revision timestamps that can indicate extensive editing.
Real-World Application: Forensic analysts use this to prove that a document was modified after signing.
When to Use: As a helper for the revision‑history detector to obtain raw timestamps.
The method scans for modified, created, and printed time tags and returns them in a map.
Map<String, String> result = new LinkedHashMap<>();
try (Metadata metadata = new Metadata(path)) {
if (metadata.getFileFormat() == FileFormat.Unknown) return result;
for (MetadataProperty p : metadata.findProperties(
new ContainsTagSpecification(Tags.getTime().getModified())
.or(new ContainsTagSpecification(Tags.getTime().getCreated()))
.or(new ContainsTagSpecification(Tags.getTime().getPrinted())))) {
String value = "";
if (p.getValue() != null && p.getValue().getRawValue() != null) {
value = String.valueOf(p.getValue().getRawValue());
}
result.put(p.getName(), value);
}
}
return result;What This Solves: Provides a reusable routine for gathering time‑based metadata.
Real-World Application: Enables automated generation of edit‑history logs for compliance.
When to Use: When downstream tools expect tabular data, such as Excel or SIEM ingestion pipelines.
The method builds a CSV string with columns for change type, property, old value, and new value, then writes it to the specified path.
StringBuilder sb = new StringBuilder();
sb.append("change_type,property,old_value,new_value\n");
for (Map.Entry<String, String> e : diff.added.entrySet()) {
sb.append("added,").append(esc(e.getKey())).append(",,").append(esc(e.getValue())).append("\n");
}
for (Map.Entry<String, String> e : diff.removed.entrySet()) {
sb.append("removed,").append(esc(e.getKey())).append(",").append(esc(e.getValue())).append(",\n");
}
for (Map.Entry<String, String[]> e : diff.changed.entrySet()) {
sb.append("changed,").append(esc(e.getKey())).append(",")
.append(esc(e.getValue()[0])).append(",").append(esc(e.getValue()[1])).append("\n");
}
Files.write(Paths.get(outputPath), sb.toString().getBytes(StandardCharsets.UTF_8));What This Solves: Generates a ready‑to‑import CSV without custom parsers.
Real-World Application: Auditors can drop the file into Excel for quick review.
When to Use: Internally by the CSV exporter to handle commas, quotes, and newlines.
The helper checks for special characters and wraps the value in quotes, doubling any embedded quotes.
if (s == null || s.isEmpty()) return "";
if (s.contains(",") || s.contains("\"") || s.contains("\n")) {
return "\"" + s.replace("\"", "\"\"") + "\"";
}
return s;What This Solves: Prevents malformed CSV rows caused by unescaped delimiters.
Real-World Application: Ensures audit logs remain parse‑able by any CSV‑aware system.
When to Use: When downstream systems prefer structured JSON, such as web dashboards or REST APIs.
The method assembles a JSON object with three top‑level maps (added, removed, changed) and writes it to a file.
StringBuilder sb = new StringBuilder();
sb.append("{\n");
sb.append(" \"added\": {\n");
writeMap(sb, diff.added);
sb.append(" },\n");
sb.append(" \"removed\": {\n");
writeMap(sb, diff.removed);
sb.append(" },\n");
sb.append(" \"changed\": {\n");
int i = 0;
for (Map.Entry<String, String[]> e : diff.changed.entrySet()) {
String comma = ++i < diff.changed.size() ? "," : "";
sb.append(" \"").append(escape(e.getKey())).append("\": { \"from\": \"")
.append(escape(e.getValue()[0])).append("\", \"to\": \"")
.append(escape(e.getValue()[1])).append("\" }").append(comma).append("\n");
}
sb.append(" }\n");
sb.append("}\n");
Files.write(Paths.get(outputPath), sb.toString().getBytes(StandardCharsets.UTF_8));What This Solves: Provides a stable JSON schema that can be diffed across audits.
Real-World Application: Compliance dashboards can ingest the file to display change timelines.
When to Use: Internal helper for the JSON exporter to format map entries with proper commas and indentation.
The method iterates over entries, escapes keys and values, and appends them to the builder.
int i = 0;
for (Map.Entry<String, String> e : map.entrySet()) {
String comma = ++i < map.size() ? "," : "";
sb.append(" \"").append(escape(e.getKey())).append("\": \"")
.append(escape(e.getValue())).append("\"").append(comma).append("\n");
}What This Solves: Guarantees correctly formatted JSON objects without manual string concatenation errors.
Real-World Application: Used whenever a new map of metadata needs to be added to the JSON audit report.
When to Use: Called by JSON helpers to ensure quotes and backslashes do not break the JSON syntax.
The method replaces backslashes with double backslashes and escapes double quotes.
if (s == null) return "";
return s.replace("\\", "\\\\").replace("\"", "\\\"");What This Solves: Prevents malformed JSON when metadata contains characters like " or \.
Real-World Application: Guarantees that audit logs can be parsed by any JSON parser.
When to Use: As the foundational step for any diff or audit operation.
The method opens the document, iterates over all discovered properties, and records their raw or interpreted values.
Map<String, String> result = new LinkedHashMap<>();
try (Metadata metadata = new Metadata(documentPath)) {
if (metadata.getFileFormat() == FileFormat.Unknown) {
return result;
}
for (MetadataProperty p : metadata.findProperties(new NamedPropertySpec())) {
String value = "";
if (p.getInterpretedValue() != null) {
Object raw = p.getInterpretedValue().getRawValue();
if (raw != null) value = String.valueOf(raw);
} else if (p.getValue() != null) {
Object raw = p.getValue().getRawValue();
if (raw != null) value = String.valueOf(raw);
}
result.put(p.getName(), value);
}
}
return result;What This Solves: Provides a single method to harvest all metadata, simplifying downstream diff logic.
Real-World Application: Used by auditors to create a baseline snapshot of a document's metadata.
By using GroupDocs.Metadata for compare-metadata-between-document-versions-java, you gain:
- Speed: Full metadata extraction runs in under a second for typical Office files.
- Accuracy: Library supports over 150 file formats, ensuring no hidden tags are missed.
- Automation: Exporters produce ready‑to‑consume CSV/JSON reports for CI pipelines.
- Compliance: Generates audit‑grade logs that satisfy GDPR and ISO 27001 requirements.
- Extensibility: Code is modular; you can plug in additional exporters or custom tag specs.
GroupDocs.Metadata, Java, metadata diff, document forensics, ownership detection, revision history, CSV export, JSON export, legal e‑discovery, compliance audit, metadata extraction, property comparison, document versioning, metadata API, metadata analysis, metadata reporting, metadata change detection, metadata audit, metadata tools, metadata library, metadata SDK
Ready to get started? View Documentation | Get Support | Request License