Skip to content

groupdocs-metadata/compare-document-metadata-versions-using-groupdocs-metadata-java

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 

Repository files navigation

compare-metadata-between-document-versions-java

Product Page Docs Blog Free Support Temporary License

Introduction

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.

When should I use this demo?

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.

Use Case Scenarios

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.

The Problem

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.

Common Challenges

  • ❌ 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.

The Solution

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.

Implementation Workflow

Here's a typical workflow for implementing compare-metadata-between-document-versions-java:

  1. Load documents: Open both versions with Metadata objects.
  2. Extract full metadata: Use ExtractAllMetadata.run to build name‑value maps.
  3. Compute diff: Call CompareMetadataSets.run to get added/removed/changed entries.
  4. Analyze ownership: Run DetectOwnershipChanges.run for creator/editor changes.
  5. Export report: Serialize the diff with ExportDiffToCsv.run or ExportDiffToJson.run.

Requirements

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.

Project Structure

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.

Practical Examples

Use Case: Compares metadata between two document versions and returns a structured diff.

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.

Use Case: Detects changes in document ownership and authorship between two 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.

Use Case: Extracts ownership‑related metadata properties from a document.

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.

Use Case: Extracts revision‑related metadata properties from a document.

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.

Use Case: Serializes a MetadataDiff into a CSV audit report on disk.

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.

Use Case: Escapes a string for safe inclusion in CSV output.

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.

Use Case: Serializes a MetadataDiff into a human‑readable JSON audit report on disk.

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.

Use Case: Writes a map of string key‑value pairs to a StringBuilder in JSON format.

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.

Use Case: Escapes special characters in a string for safe JSON output.

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.

Use Case: Extracts every accessible metadata property from a document into a name → value map.

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.

Benefits

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.

Keywords

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

About

GroupDocs.Metadata for Java sample compares metadata of two document versions, shows added, removed and changed fields, and exports the diff to CSV or JSON.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages