This is an automated email from the ASF dual-hosted git repository.

tballison pushed a commit to branch TIKA-4809-metadata-key-api
in repository https://gitbox.apache.org/repos/asf/tika.git

commit 515e602b17e3786f26d5b361d72e47384f86a399
Author: tallison <[email protected]>
AuthorDate: Tue Aug 11 13:40:27 2026 -0400

    TIKA-4809 metadata-key stage 3: KeyPrefix rename + constructor validation + 
typed minting factories
---
 .skills/metadata-schema.md                         |   6 +-
 docs/modules/ROOT/pages/developers/index.adoc      |   2 +-
 .../ROOT/pages/developers/metadata-keys.adoc       |   6 +-
 .../main/java/org/apache/tika/metadata/DWG.java    |   4 +-
 .../main/java/org/apache/tika/metadata/HTML.java   |   2 +-
 .../java/org/apache/tika/metadata/KeyPrefix.java   | 172 +++++++++++++++++++
 .../main/java/org/apache/tika/metadata/MAPI.java   |   2 +-
 .../java/org/apache/tika/metadata/Message.java     |   2 +-
 .../main/java/org/apache/tika/metadata/Office.java |   2 +-
 .../main/java/org/apache/tika/metadata/PDF.java    |   4 +-
 .../apache/tika/metadata/PassthroughPrefix.java    |  79 ---------
 .../java/org/apache/tika/metadata/RTFMetadata.java |   2 +-
 .../org/apache/tika/metadata/KeyPrefixTest.java    | 182 +++++++++++++++++++++
 tika-metadata-schema/README.md                     |   6 +-
 tika-metadata-schema/regen.sh                      |   8 +-
 .../tika/metadata/schema/SchemaGenerator.java      |  12 +-
 .../tika/metadata/schema/MetadataCoverageTest.java |   4 +-
 .../tika/metadata/schema/MetadataSchemaTest.java   |   4 +-
 .../apache/tika/parser/envi/EnviHeaderParser.java  |   6 +-
 .../org/apache/tika/parser/grib/GribParser.java    |   6 +-
 .../apache/tika/parser/netcdf/NetCDFParser.java    |   6 +-
 .../tika/parser/ctakes/CTAKESContentHandler.java   |   6 +-
 .../tika/parser/journal/GrobidRESTParser.java      |   6 +-
 .../apache/tika/parser/ner/NamedEntityParser.java  |   6 +-
 .../tika/parser/iwork/NumbersContentHandler.java   |   6 +-
 .../parser/iwork/iwana/IWork13PackageParser.java   |   6 +-
 .../org/apache/tika/parser/ogg/OggAudioParser.java |   6 +-
 .../org/apache/tika/parser/video/FLVParser.java    |   6 +-
 .../java/org/apache/tika/parser/dwg/DWGParser.java |   6 +-
 .../tika/parser/image/ImageMetadataExtractor.java  |  10 +-
 .../org/apache/tika/parser/mbox/MboxParser.java    |   6 +-
 .../tika/parser/microsoft/JackcessParser.java      |  14 +-
 .../org/apache/tika/parser/warc/WARCParser.java    |  10 +-
 .../org/apache/tika/parser/xmp/XmpExtractor.java   |   4 +-
 34 files changed, 442 insertions(+), 167 deletions(-)

diff --git a/.skills/metadata-schema.md b/.skills/metadata-schema.md
index da51bf1fdb..a31c02cc74 100644
--- a/.skills/metadata-schema.md
+++ b/.skills/metadata-schema.md
@@ -9,19 +9,19 @@ this covers conventions, regeneration, and the traps.
 Under `tika-metadata-schema/src/main/resources/org/apache/tika/metadata/`:
 
 - `metadata-keys.json` — closed set: every `Property` constant + the 
synthesized `tk:digest:*` cross-product.
-- `metadata-open-namespaces.json` — `PassthroughPrefix` prefixes for 
runtime-minted names (`html:`, `message:raw-header:`, `mdb-prop:`).
+- `metadata-open-namespaces.json` — `KeyPrefix` prefixes for runtime-minted 
names (`html:`, `message:raw-header:`, `mdb-prop:`).
 - `metadata-key-fields.json` — TIKA-4797 `{class, field, key}` table for 
field-identity migration.
 
 Committed on purpose: they are the reviewable audit trail of the key space (a 
rename or a dropped key
 shows up as a diff). Don't switch to build-time-only generation — that loses 
the review signal.
 
-## Regenerate (after adding/changing a Property or PassthroughPrefix)
+## Regenerate (after adding/changing a Property or KeyPrefix)
 
 ```bash
 tika-metadata-schema/regen.sh
 ```
 
-This does the full sequence in one shot: `-am install` so newly added 
Property/PassthroughPrefix
+This does the full sequence in one shot: `-am install` so newly added 
Property/KeyPrefix
 classes are on the scan classpath, regenerate all three registries via the 
forked-exec profile, print
 a before/after key-count check (catches an incomplete classpath scan), `git 
diff --stat` the
 registries, then run the gate tests. Flags: `--skip-install` (only safe if 
nothing outside
diff --git a/docs/modules/ROOT/pages/developers/index.adoc 
b/docs/modules/ROOT/pages/developers/index.adoc
index ac1ef574be..95701969c6 100644
--- a/docs/modules/ROOT/pages/developers/index.adoc
+++ b/docs/modules/ROOT/pages/developers/index.adoc
@@ -22,7 +22,7 @@ with custom parsers, detectors, and other components.
 
 * xref:developers/serialization.adoc[Serialization and Configuration] - JSON 
configuration,
   @TikaComponent annotation, and creating custom components
-* xref:developers/metadata-keys.adoc[Adding a Metadata Key] - the 
Property/PassthroughPrefix
+* xref:developers/metadata-keys.adoc[Adding a Metadata Key] - the 
Property/KeyPrefix
   registry, naming conventions, and regenerating the schema
 
 == Coming Soon
diff --git a/docs/modules/ROOT/pages/developers/metadata-keys.adoc 
b/docs/modules/ROOT/pages/developers/metadata-keys.adoc
index 7d5deeb4f7..71ca570b70 100644
--- a/docs/modules/ROOT/pages/developers/metadata-keys.adoc
+++ b/docs/modules/ROOT/pages/developers/metadata-keys.adoc
@@ -16,7 +16,7 @@
 = Adding a Metadata Key
 
 Every metadata key Tika can emit is a `Property` constant (or, for 
runtime-minted names like scraped
-HTML `<meta>` tags, a `PassthroughPrefix`) — there are no bare `String` keys. 
That closed/open key
+HTML `<meta>` tags, a `KeyPrefix`) — there are no bare `String` keys. That 
closed/open key
 space is tracked in a generated, build-gated registry, so adding a key 
involves one extra step beyond
 writing the Java.
 
@@ -40,10 +40,10 @@ Naming conventions (frozen for 4.0):
 
 The registry — three JSON files under 
`tika-metadata-schema/src/main/resources/`, listing every
 declared key, every open-namespace prefix, and a field-provenance table — is 
generated from the live
-`Property`/`PassthroughPrefix` declarations, never hand-edited. A committed 
copy is the reviewable
+`Property`/`KeyPrefix` declarations, never hand-edited. A committed copy is 
the reviewable
 audit trail (a rename or dropped key shows up as a diff), and CI fails if it's 
stale.
 
-Run this after adding, renaming, or removing a `Property` or 
`PassthroughPrefix`:
+Run this after adding, renaming, or removing a `Property` or `KeyPrefix`:
 
 [source,bash]
 ----
diff --git a/tika-core/src/main/java/org/apache/tika/metadata/DWG.java 
b/tika-core/src/main/java/org/apache/tika/metadata/DWG.java
index 67a51a8732..a2b70918ff 100644
--- a/tika-core/src/main/java/org/apache/tika/metadata/DWG.java
+++ b/tika-core/src/main/java/org/apache/tika/metadata/DWG.java
@@ -23,8 +23,8 @@ public interface DWG {
 
     String DWG_PREFIX = "dwg" + TikaCoreProperties.NAMESPACE_PREFIX_DELIMITER;
 
-    PassthroughPrefix RAW_FIELD =
-            PassthroughPrefix.file(DWG_PREFIX, "DWGRead JSON header/summary 
field names");
+    KeyPrefix RAW_FIELD =
+            KeyPrefix.file(DWG_PREFIX, "DWGRead JSON header/summary field 
names");
 
     Property APPLICATION_NAME = Property.externalText(DWG_PREFIX + 
"application-name");
 
diff --git a/tika-core/src/main/java/org/apache/tika/metadata/HTML.java 
b/tika-core/src/main/java/org/apache/tika/metadata/HTML.java
index a26c979479..97c45da5e3 100644
--- a/tika-core/src/main/java/org/apache/tika/metadata/HTML.java
+++ b/tika-core/src/main/java/org/apache/tika/metadata/HTML.java
@@ -19,7 +19,7 @@ package org.apache.tika.metadata;
 public interface HTML {
     String PREFIX_HTML_META = "html" + 
TikaCoreProperties.NAMESPACE_PREFIX_DELIMITER;
 
-    PassthroughPrefix SCRAPED_META = PassthroughPrefix.file(PREFIX_HTML_META,
+    KeyPrefix SCRAPED_META = KeyPrefix.file(PREFIX_HTML_META,
             "scraped <meta>/http-equiv/OpenGraph names not mapped to a 
Property");
 
 
diff --git a/tika-core/src/main/java/org/apache/tika/metadata/KeyPrefix.java 
b/tika-core/src/main/java/org/apache/tika/metadata/KeyPrefix.java
new file mode 100644
index 0000000000..bf244164c6
--- /dev/null
+++ b/tika-core/src/main/java/org/apache/tika/metadata/KeyPrefix.java
@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tika.metadata;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.tika.metadata.Property.PropertyType;
+import org.apache.tika.metadata.Property.ValueType;
+
+/**
+ * A Tika-owned prefix under which keys are passed through from the source: 
the prefix is fixed, but
+ * each key name comes verbatim from the document or tool, so keys are 
unbounded and can't be
+ * {@link Property} constants. Declaring one self-registers it, so the open 
set is enumerable (as
+ * {@link Property} makes the closed set) and lintable: a String write is 
legitimate iff its key is a
+ * registered {@link Property} or its prefix is a registered {@code KeyPrefix}.
+ *
+ * <p><strong>{@code KeyPrefix} instances are declaration-time 
constants.</strong> Declare one as a
+ * {@code static final} field, the same way a curated {@link Property} 
constant is declared — never
+ * construct one from document-derived text, and never construct one 
per-parse: the registry is
+ * static and unbounded per-parse construction grows it forever (and now also 
throws, see below).
+ *
+ * @since Apache Tika 4.0.0
+ */
+public final class KeyPrefix {
+
+    public enum Provenance { FILE, TOOL }
+
+    /** Delimiters accepted at the end of a prefix; kept intentionally loose 
(some declarations
+     * predate a single convention) — see {@code ReservedNamespaces} for the 
reserved-name check. */
+    private static final char[] TRAILING_DELIMITERS = {':', '.', '-', '_'};
+
+    private static final Map<String, KeyPrefix> REGISTRY = new 
ConcurrentHashMap<>();
+
+    private final String prefix;
+    private final Provenance provenance;
+    private final String description;
+
+    private KeyPrefix(String prefix, Provenance provenance, String 
description) {
+        if (prefix == null || prefix.isEmpty()) {
+            throw new IllegalArgumentException("KeyPrefix prefix must not be 
null or empty");
+        }
+        if (ReservedNamespaces.isTikaNative(prefix)) {
+            throw new IllegalArgumentException("'" + prefix + "' is in the 
reserved Tika-native "
+                    + "namespace (tk:/X-TIKA:); it cannot be a KeyPrefix");
+        }
+        if (!hasTrailingDelimiter(prefix)) {
+            throw new IllegalArgumentException("'" + prefix + "' must end with 
one of "
+                    + Arrays.toString(TRAILING_DELIMITERS));
+        }
+        this.prefix = prefix;
+        this.provenance = provenance;
+        this.description = description;
+        KeyPrefix incumbent = REGISTRY.putIfAbsent(prefix, this);
+        if (incumbent != null) {
+            throw new IllegalStateException(
+                    "KeyPrefix '" + prefix + "' is already registered; 
KeyPrefix instances are "
+                            + "declaration-time constants and must be 
constructed exactly once "
+                            + "(never from document-derived text, never 
per-parse)");
+        }
+    }
+
+    private static boolean hasTrailingDelimiter(String prefix) {
+        char last = prefix.charAt(prefix.length() - 1);
+        for (char d : TRAILING_DELIMITERS) {
+            if (d == last) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static KeyPrefix file(String prefix, String description) {
+        return new KeyPrefix(prefix, Provenance.FILE, description);
+    }
+
+    public static KeyPrefix tool(String prefix, String description) {
+        return new KeyPrefix(prefix, Provenance.TOOL, description);
+    }
+
+    /** The full key for a source-derived {@code suffix}. */
+    public String key(String suffix) {
+        return prefix + suffix;
+    }
+
+    public String prefix() {
+        return prefix;
+    }
+
+    public Provenance provenance() {
+        return provenance;
+    }
+
+    public String description() {
+        return description;
+    }
+
+    /** Declared prefixes, from loaded classes only. */
+    public static Collection<KeyPrefix> registered() {
+        return Collections.unmodifiableCollection(REGISTRY.values());
+    }
+
+    // ---- Typed minting factories 
-----------------------------------------------------
+    // Each mints an UNREGISTERED Property (Property.mintUnregistered) named 
prefix + name,
+    // shaped like the corresponding external* Property factory. Mint per call 
for doc-derived
+    // names: minted Properties are cheap, unregistered value objects, GC'd 
with the parse.
+    // A bounded, known vocabulary belongs in a curated Property constant 
instead (that IS the
+    // cache, with registry/CI coverage) — never build a name-keyed static 
cache here, that
+    // recreates the registry-growth leak this design avoids.
+
+    private static void requireNonEmptyName(String name) {
+        if (name == null || name.isEmpty()) {
+            throw new IllegalArgumentException("KeyPrefix-minted Property name 
must not be null "
+                    + "or empty");
+        }
+    }
+
+    /** Mints an unregistered SIMPLE/TEXT Property, like {@link 
Property#externalText}. */
+    public Property text(String name) {
+        requireNonEmptyName(name);
+        return Property.mintUnregistered(key(name), false, 
PropertyType.SIMPLE, ValueType.TEXT, null);
+    }
+
+    /** Mints an unregistered BAG/TEXT Property, like {@link 
Property#externalTextBag}. */
+    public Property textBag(String name) {
+        requireNonEmptyName(name);
+        return Property.mintUnregistered(key(name), false, PropertyType.BAG, 
ValueType.TEXT, null);
+    }
+
+    /** Mints an unregistered SIMPLE/DATE Property, like {@link 
Property#externalDate}. */
+    public Property date(String name) {
+        requireNonEmptyName(name);
+        return Property.mintUnregistered(key(name), false, 
PropertyType.SIMPLE, ValueType.DATE, null);
+    }
+
+    /** Mints an unregistered SIMPLE/INTEGER Property, like {@link 
Property#externalInteger}. */
+    public Property integer(String name) {
+        requireNonEmptyName(name);
+        return Property.mintUnregistered(key(name), false, 
PropertyType.SIMPLE, ValueType.INTEGER,
+                null);
+    }
+
+    /** Mints an unregistered SIMPLE/REAL Property, like {@link 
Property#externalReal}. */
+    public Property real(String name) {
+        requireNonEmptyName(name);
+        return Property.mintUnregistered(key(name), false, 
PropertyType.SIMPLE, ValueType.REAL, null);
+    }
+
+    /** Mints an unregistered SIMPLE/BOOLEAN Property, like {@link 
Property#externalBoolean}. */
+    public Property bool(String name) {
+        requireNonEmptyName(name);
+        return Property.mintUnregistered(key(name), false, 
PropertyType.SIMPLE, ValueType.BOOLEAN,
+                null);
+    }
+}
diff --git a/tika-core/src/main/java/org/apache/tika/metadata/MAPI.java 
b/tika-core/src/main/java/org/apache/tika/metadata/MAPI.java
index 74657ce85b..5b9b940913 100644
--- a/tika-core/src/main/java/org/apache/tika/metadata/MAPI.java
+++ b/tika-core/src/main/java/org/apache/tika/metadata/MAPI.java
@@ -27,7 +27,7 @@ public interface MAPI {
     String PREFIX_MAPI_META = "mapi" + 
TikaCoreProperties.NAMESPACE_PREFIX_DELIMITER;
     String PREFIX_MAPI_ATTACH_META = "mapi:attach" + 
TikaCoreProperties.NAMESPACE_PREFIX_DELIMITER;
     String PREFIX_MAPI_PROPERTY = PREFIX_MAPI_META + "property" + 
TikaCoreProperties.NAMESPACE_PREFIX_DELIMITER;
-    PassthroughPrefix PROPERTY = PassthroughPrefix.file(PREFIX_MAPI_PROPERTY, 
"MAPI named properties");
+    KeyPrefix PROPERTY = KeyPrefix.file(PREFIX_MAPI_PROPERTY, "MAPI named 
properties");
 
     /**
      * MAPI message class.  What type of .msg/MAPI file is it?
diff --git a/tika-core/src/main/java/org/apache/tika/metadata/Message.java 
b/tika-core/src/main/java/org/apache/tika/metadata/Message.java
index cf711eeec8..e069a396a6 100644
--- a/tika-core/src/main/java/org/apache/tika/metadata/Message.java
+++ b/tika-core/src/main/java/org/apache/tika/metadata/Message.java
@@ -29,7 +29,7 @@ public interface Message {
     String MESSAGE_RAW_HEADER_PREFIX =
             MESSAGE_PREFIX + "raw-header" + 
TikaCoreProperties.NAMESPACE_PREFIX_DELIMITER;
 
-    PassthroughPrefix RAW_HEADER = 
PassthroughPrefix.file(MESSAGE_RAW_HEADER_PREFIX,
+    KeyPrefix RAW_HEADER = KeyPrefix.file(MESSAGE_RAW_HEADER_PREFIX,
             "RFC822 / Outlook raw email header names");
 
     Property MESSAGE_RECIPIENT_ADDRESS =
diff --git a/tika-core/src/main/java/org/apache/tika/metadata/Office.java 
b/tika-core/src/main/java/org/apache/tika/metadata/Office.java
index 2a348f45d9..f2d7ccc686 100644
--- a/tika-core/src/main/java/org/apache/tika/metadata/Office.java
+++ b/tika-core/src/main/java/org/apache/tika/metadata/Office.java
@@ -37,7 +37,7 @@ public interface Office {
      */
     String USER_DEFINED_METADATA_NAME_PREFIX = "custom:";
 
-    PassthroughPrefix USER_DEFINED = 
PassthroughPrefix.file(USER_DEFINED_METADATA_NAME_PREFIX,
+    KeyPrefix USER_DEFINED = KeyPrefix.file(USER_DEFINED_METADATA_NAME_PREFIX,
             "OOXML/OLE2/ODF user-defined document properties");
 
 
diff --git a/tika-core/src/main/java/org/apache/tika/metadata/PDF.java 
b/tika-core/src/main/java/org/apache/tika/metadata/PDF.java
index c7bda2fdf4..08f2645208 100644
--- a/tika-core/src/main/java/org/apache/tika/metadata/PDF.java
+++ b/tika-core/src/main/java/org/apache/tika/metadata/PDF.java
@@ -51,8 +51,8 @@ public interface PDF {
     String PDF_DOC_INFO_CUSTOM_PREFIX =
             PDF_DOC_INFO_PREFIX + "custom" + 
TikaCoreProperties.NAMESPACE_PREFIX_DELIMITER;
 
-    PassthroughPrefix DOC_INFO_CUSTOM =
-            PassthroughPrefix.file(PDF_DOC_INFO_CUSTOM_PREFIX, "PDF Info-dict 
custom keys");
+    KeyPrefix DOC_INFO_CUSTOM =
+            KeyPrefix.file(PDF_DOC_INFO_CUSTOM_PREFIX, "PDF Info-dict custom 
keys");
 
     Property DOC_INFO_CREATED = Property.internalDate(PDF_DOC_INFO_PREFIX + 
"created");
 
diff --git 
a/tika-core/src/main/java/org/apache/tika/metadata/PassthroughPrefix.java 
b/tika-core/src/main/java/org/apache/tika/metadata/PassthroughPrefix.java
deleted file mode 100644
index ea17d3b555..0000000000
--- a/tika-core/src/main/java/org/apache/tika/metadata/PassthroughPrefix.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.tika.metadata;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-/**
- * A Tika-owned prefix under which keys are passed through from the source: 
the prefix is fixed, but
- * each key name comes verbatim from the document or tool, so keys are 
unbounded and can't be
- * {@link Property} constants. Declaring one self-registers it, so the open 
set is enumerable (as
- * {@link Property} makes the closed set) and lintable: a String write is 
legitimate iff its key is a
- * registered {@link Property} or its prefix is a registered {@code 
PassthroughPrefix}.
- *
- * @since Apache Tika 4.0.0
- */
-public final class PassthroughPrefix {
-
-    public enum Provenance { FILE, TOOL }
-
-    private static final Map<String, PassthroughPrefix> REGISTRY = new 
ConcurrentHashMap<>();
-
-    private final String prefix;
-    private final Provenance provenance;
-    private final String description;
-
-    private PassthroughPrefix(String prefix, Provenance provenance, String 
description) {
-        this.prefix = prefix;
-        this.provenance = provenance;
-        this.description = description;
-        REGISTRY.put(prefix, this);
-    }
-
-    public static PassthroughPrefix file(String prefix, String description) {
-        return new PassthroughPrefix(prefix, Provenance.FILE, description);
-    }
-
-    public static PassthroughPrefix tool(String prefix, String description) {
-        return new PassthroughPrefix(prefix, Provenance.TOOL, description);
-    }
-
-    /** The full key for a source-derived {@code suffix}. */
-    public String key(String suffix) {
-        return prefix + suffix;
-    }
-
-    public String prefix() {
-        return prefix;
-    }
-
-    public Provenance provenance() {
-        return provenance;
-    }
-
-    public String description() {
-        return description;
-    }
-
-    /** Declared prefixes, from loaded classes only. */
-    public static Collection<PassthroughPrefix> registered() {
-        return Collections.unmodifiableCollection(REGISTRY.values());
-    }
-}
diff --git a/tika-core/src/main/java/org/apache/tika/metadata/RTFMetadata.java 
b/tika-core/src/main/java/org/apache/tika/metadata/RTFMetadata.java
index 94aefbc85b..f1eb58d213 100644
--- a/tika-core/src/main/java/org/apache/tika/metadata/RTFMetadata.java
+++ b/tika-core/src/main/java/org/apache/tika/metadata/RTFMetadata.java
@@ -26,7 +26,7 @@ public interface RTFMetadata {
     String RTF_PICT_META_PREFIX = "rtf:pict:";
 
     /** Open (file-controlled) RTF embedded-object property names built off 
{@link #RTF_PICT_META_PREFIX}. */
-    PassthroughPrefix PICT = PassthroughPrefix.file(RTF_PICT_META_PREFIX,
+    KeyPrefix PICT = KeyPrefix.file(RTF_PICT_META_PREFIX,
             "RTF embedded picture-object property pairs (sn/sv); 
file-controlled names");
 
     /**
diff --git 
a/tika-core/src/test/java/org/apache/tika/metadata/KeyPrefixTest.java 
b/tika-core/src/test/java/org/apache/tika/metadata/KeyPrefixTest.java
new file mode 100644
index 0000000000..25bd8e29d9
--- /dev/null
+++ b/tika-core/src/test/java/org/apache/tika/metadata/KeyPrefixTest.java
@@ -0,0 +1,182 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tika.metadata;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+import org.apache.tika.metadata.Property.PropertyType;
+import org.apache.tika.metadata.Property.ValueType;
+
+/**
+ * Stage 3: the {@code PassthroughPrefix} -> {@code KeyPrefix} rename, 
constructor validation,
+ * and the typed unregistered-Property minting factories.
+ */
+public class KeyPrefixTest {
+
+    // Every registered prefix is a unique, unrepeatable static registration 
-- give each test
+    // its own prefix (nanoTime-suffixed, following PropertyReservedNameTest's 
convention) so
+    // tests never collide with each other or with real parser-declared 
prefixes.
+    private static String uniquePrefix(String label) {
+        return "keyprefix-test-" + label + "-" + System.nanoTime() + ":";
+    }
+
+    @Test
+    public void testRejectsNullPrefix() {
+        assertThrows(IllegalArgumentException.class, () -> 
KeyPrefix.file(null, "d"));
+    }
+
+    @Test
+    public void testRejectsEmptyPrefix() {
+        assertThrows(IllegalArgumentException.class, () -> KeyPrefix.file("", 
"d"));
+    }
+
+    @Test
+    public void testRejectsReservedTkPrefix() {
+        assertThrows(IllegalArgumentException.class, () -> 
KeyPrefix.file("tk:", "d"));
+    }
+
+    @Test
+    public void testRejectsReservedLegacyXTikaPrefix() {
+        assertThrows(IllegalArgumentException.class, () -> 
KeyPrefix.file("X-TIKA:", "d"));
+    }
+
+    @Test
+    public void testRejectsMissingTrailingDelimiter() {
+        String prefix = "keyprefix-test-no-delim-" + System.nanoTime();
+        assertThrows(IllegalArgumentException.class, () -> 
KeyPrefix.file(prefix, "d"));
+    }
+
+    @Test
+    public void testAcceptsAllCurrentlyUsedTrailingDelimiters() {
+        // ':' '.' '-' '_' -- the population enumerated pre-implementation 
(netcdf: envi.
+        // grobid:header_ MboxParser- etc). A later stage normalizes these to 
':'; stage 3 must
+        // not break the still-live population.
+        for (char delim : new char[]{':', '.', '-', '_'}) {
+            String prefix = uniquePrefix("delim-" + delim) + delim;
+            KeyPrefix.file(prefix, "d"); // must not throw
+        }
+    }
+
+    @Test
+    public void testDuplicateRegistrationThrows() {
+        String prefix = uniquePrefix("dup");
+        KeyPrefix.file(prefix, "first");
+        assertThrows(IllegalStateException.class, () -> KeyPrefix.tool(prefix, 
"second"));
+    }
+
+    @Test
+    public void testKeyUnchanged() {
+        KeyPrefix kp = KeyPrefix.file(uniquePrefix("key"), "d");
+        assertEquals(kp.prefix() + "suffix", kp.key("suffix"));
+    }
+
+    @Test
+    public void testFileProvenancePreserved() {
+        KeyPrefix kp = KeyPrefix.file(uniquePrefix("prov-file"), "d");
+        assertEquals(KeyPrefix.Provenance.FILE, kp.provenance());
+    }
+
+    @Test
+    public void testToolProvenancePreserved() {
+        KeyPrefix kp = KeyPrefix.tool(uniquePrefix("prov-tool"), "d");
+        assertEquals(KeyPrefix.Provenance.TOOL, kp.provenance());
+    }
+
+    @Test
+    public void testTextFactoryShape() {
+        KeyPrefix kp = KeyPrefix.file(uniquePrefix("text"), "d");
+        Property p = kp.text("name");
+        assertEquals(kp.prefix() + "name", p.getName());
+        assertEquals(PropertyType.SIMPLE, p.getPropertyType());
+        assertEquals(ValueType.TEXT, p.getValueType());
+        assertNull(Property.get(p.getName()), "minted Property must not 
register");
+    }
+
+    @Test
+    public void testTextBagFactoryShape() {
+        KeyPrefix kp = KeyPrefix.file(uniquePrefix("textbag"), "d");
+        Property p = kp.textBag("name");
+        assertEquals(kp.prefix() + "name", p.getName());
+        assertEquals(PropertyType.BAG, p.getPropertyType());
+        assertEquals(ValueType.TEXT, p.getValueType());
+        assertNull(Property.get(p.getName()));
+    }
+
+    @Test
+    public void testDateFactoryShape() {
+        KeyPrefix kp = KeyPrefix.file(uniquePrefix("date"), "d");
+        Property p = kp.date("name");
+        assertEquals(kp.prefix() + "name", p.getName());
+        assertEquals(PropertyType.SIMPLE, p.getPropertyType());
+        assertEquals(ValueType.DATE, p.getValueType());
+        assertNull(Property.get(p.getName()));
+    }
+
+    @Test
+    public void testIntegerFactoryShape() {
+        KeyPrefix kp = KeyPrefix.file(uniquePrefix("integer"), "d");
+        Property p = kp.integer("name");
+        assertEquals(kp.prefix() + "name", p.getName());
+        assertEquals(PropertyType.SIMPLE, p.getPropertyType());
+        assertEquals(ValueType.INTEGER, p.getValueType());
+        assertNull(Property.get(p.getName()));
+    }
+
+    @Test
+    public void testRealFactoryShape() {
+        KeyPrefix kp = KeyPrefix.file(uniquePrefix("real"), "d");
+        Property p = kp.real("name");
+        assertEquals(kp.prefix() + "name", p.getName());
+        assertEquals(PropertyType.SIMPLE, p.getPropertyType());
+        assertEquals(ValueType.REAL, p.getValueType());
+        assertNull(Property.get(p.getName()));
+    }
+
+    @Test
+    public void testBoolFactoryShape() {
+        KeyPrefix kp = KeyPrefix.file(uniquePrefix("bool"), "d");
+        Property p = kp.bool("name");
+        assertEquals(kp.prefix() + "name", p.getName());
+        assertEquals(PropertyType.SIMPLE, p.getPropertyType());
+        assertEquals(ValueType.BOOLEAN, p.getValueType());
+        assertNull(Property.get(p.getName()));
+    }
+
+    @Test
+    public void testFactoriesRejectEmptyName() {
+        KeyPrefix kp = KeyPrefix.file(uniquePrefix("empty-name"), "d");
+        assertThrows(IllegalArgumentException.class, () -> kp.text(""));
+        assertThrows(IllegalArgumentException.class, () -> kp.text(null));
+        assertThrows(IllegalArgumentException.class, () -> kp.textBag(""));
+        assertThrows(IllegalArgumentException.class, () -> kp.date(""));
+        assertThrows(IllegalArgumentException.class, () -> kp.integer(""));
+        assertThrows(IllegalArgumentException.class, () -> kp.real(""));
+        assertThrows(IllegalArgumentException.class, () -> kp.bool(""));
+    }
+
+    @Test
+    public void testRegisteredIncludesDeclaredPrefix() {
+        String prefix = uniquePrefix("registered");
+        KeyPrefix kp = KeyPrefix.file(prefix, "d");
+        assertTrue(KeyPrefix.registered().contains(kp));
+    }
+}
diff --git a/tika-metadata-schema/README.md b/tika-metadata-schema/README.md
index 94968e36fa..7650ed36ca 100644
--- a/tika-metadata-schema/README.md
+++ b/tika-metadata-schema/README.md
@@ -36,7 +36,7 @@ declare a `Property` field, force-loads them, reads the 
global `Property` table,
 sorted JSON. `MetadataSchemaTest` regenerates in-memory and asserts it matches 
the committed file, so
 the registry can never drift from the declarations.
 
-Regenerate after adding/changing a `Property` **or** a `PassthroughPrefix` 
(writes all three files):
+Regenerate after adding/changing a `Property` **or** a `KeyPrefix` (writes all 
three files):
 ```
 tika-metadata-schema/regen.sh
 ```
@@ -50,8 +50,8 @@ The **prefixes** under which parsers mint file-controlled key 
names at runtime 
 `html:`, OOXML `custom:`, email `Message:Raw-Header:`, Access `MDB_PROP:`, 
Vorbis comments, FLV
 attributes, unmapped image/XMP tags, …). Each record: `{ prefix, provenance, 
description }`.
 
-**Generated from the `PassthroughPrefix` declarations, never hand-edited.** 
Every such prefix is a
-registered `PassthroughPrefix` constant; `SchemaGenerator` reads that registry 
the same way it reads
+**Generated from the `KeyPrefix` declarations, never hand-edited.** Every such 
prefix is a
+registered `KeyPrefix` constant; `SchemaGenerator` reads that registry the 
same way it reads
 the `Property` table, and `MetadataSchemaTest` gates it identically. Adding a 
passthrough prefix in a
 parser and forgetting to regenerate fails the build.
 
diff --git a/tika-metadata-schema/regen.sh b/tika-metadata-schema/regen.sh
index 4d20fa7f45..1874468e57 100755
--- a/tika-metadata-schema/regen.sh
+++ b/tika-metadata-schema/regen.sh
@@ -2,7 +2,7 @@
 #
 # Regenerates and validates the metadata key registry (tika-metadata-schema).
 #
-# Run this after adding, renaming, or removing a Property or PassthroughPrefix
+# Run this after adding, renaming, or removing a Property or KeyPrefix
 # constant anywhere in tika-core or the standard parser bundle. It replaces the
 # multi-step manual sequence in .skills/metadata-schema.md with one command:
 # install the dependency modules, regenerate the three registry files, sanity
@@ -12,7 +12,7 @@
 #   tika-metadata-schema/regen.sh [--skip-install] [--skip-tests]
 #
 #   --skip-install  skip the -am install step (only safe if no Property/
-#                   PassthroughPrefix classes outside tika-metadata-schema
+#                   KeyPrefix classes outside tika-metadata-schema
 #                   changed since the last install)
 #   --skip-tests    skip the final gate-test run, for a faster inner loop
 #
@@ -53,7 +53,7 @@ REGISTRY_FILES=(
 
 if [ "$SKIP_INSTALL" -eq 0 ]; then
     echo "==> Installing tika-metadata-schema + its dependency modules 
(tika-core, standard parsers)"
-    echo "    so newly added Property/PassthroughPrefix classes are on the 
scan classpath."
+    echo "    so newly added Property/KeyPrefix classes are on the scan 
classpath."
     echo "    (skip with --skip-install if you already did this)"
     ./mvnw -Pfast -DskipTests -pl tika-metadata-schema -am install 
"$MVN_REPO_OPT"
 fi
@@ -92,4 +92,4 @@ if [ "$SKIP_TESTS" -eq 0 ]; then
     ./mvnw -pl tika-metadata-schema test "$MVN_REPO_OPT"
 fi
 
-echo "==> Done. Review the diff above, then commit the 
Property/PassthroughPrefix change and the regenerated JSON together."
+echo "==> Done. Review the diff above, then commit the Property/KeyPrefix 
change and the regenerated JSON together."
diff --git 
a/tika-metadata-schema/src/main/java/org/apache/tika/metadata/schema/SchemaGenerator.java
 
b/tika-metadata-schema/src/main/java/org/apache/tika/metadata/schema/SchemaGenerator.java
index 8a552319cc..2be82a94b4 100644
--- 
a/tika-metadata-schema/src/main/java/org/apache/tika/metadata/schema/SchemaGenerator.java
+++ 
b/tika-metadata-schema/src/main/java/org/apache/tika/metadata/schema/SchemaGenerator.java
@@ -32,7 +32,7 @@ import java.util.jar.JarEntry;
 import java.util.jar.JarFile;
 
 import org.apache.tika.digest.DigestDef;
-import org.apache.tika.metadata.PassthroughPrefix;
+import org.apache.tika.metadata.KeyPrefix;
 import org.apache.tika.metadata.Property;
 
 /**
@@ -54,8 +54,8 @@ public final class SchemaGenerator {
     // Field/parameter descriptors in a .class constant pool: a class 
referencing one is force-loaded.
     private static final byte[] PROP_DESC =
             
"Lorg/apache/tika/metadata/Property;".getBytes(StandardCharsets.ISO_8859_1);
-    private static final byte[] PASSTHROUGH_DESC =
-            
"Lorg/apache/tika/metadata/PassthroughPrefix;".getBytes(StandardCharsets.ISO_8859_1);
+    private static final byte[] KEY_PREFIX_DESC =
+            
"Lorg/apache/tika/metadata/KeyPrefix;".getBytes(StandardCharsets.ISO_8859_1);
 
     private SchemaGenerator() {
     }
@@ -118,7 +118,7 @@ public final class SchemaGenerator {
         return fieldTableJson(rows);
     }
 
-    /** Scans the classpath, force-loads every 
Property/PassthroughPrefix-bearing class (static init
+    /** Scans the classpath, force-loads every Property/KeyPrefix-bearing 
class (static init
      * registers the constants), and returns the loaded class names. */
     private static List<String> scanClasspath(ClassLoader cl) throws 
IOException {
         List<String> loaded = new ArrayList<>();
@@ -160,7 +160,7 @@ public final class SchemaGenerator {
 
     private static void maybeLoad(String classPath, byte[] bytes, ClassLoader 
cl, List<String> loaded) {
         if (!classPath.startsWith("org/apache/tika/")
-                || (!contains(bytes, PROP_DESC) && !contains(bytes, 
PASSTHROUGH_DESC))) {
+                || (!contains(bytes, PROP_DESC) && !contains(bytes, 
KEY_PREFIX_DESC))) {
             return;
         }
         String cn = classPath.substring(0, classPath.length() - 
6).replace('/', '.');
@@ -222,7 +222,7 @@ public final class SchemaGenerator {
     /** Declared passthrough prefixes as stable JSON. Call after {@link 
#generate()} has loaded classes. */
     public static String passthroughJson() {
         TreeMap<String, String[]> m = new TreeMap<>();
-        for (PassthroughPrefix p : PassthroughPrefix.registered()) {
+        for (KeyPrefix p : KeyPrefix.registered()) {
             m.put(p.prefix(), new String[]{p.provenance().name(), 
p.description()});
         }
         StringBuilder sb = new StringBuilder("[\n");
diff --git 
a/tika-metadata-schema/src/test/java/org/apache/tika/metadata/schema/MetadataCoverageTest.java
 
b/tika-metadata-schema/src/test/java/org/apache/tika/metadata/schema/MetadataCoverageTest.java
index 4980d770d1..eedd98975b 100644
--- 
a/tika-metadata-schema/src/test/java/org/apache/tika/metadata/schema/MetadataCoverageTest.java
+++ 
b/tika-metadata-schema/src/test/java/org/apache/tika/metadata/schema/MetadataCoverageTest.java
@@ -34,7 +34,7 @@ import org.junit.jupiter.api.Test;
  * Completeness backstop for the standard-scoped registry. {@link 
MetadataSchemaTest} proves the
  * registry matches the declarations it <em>scans</em>; this proves nothing 
<em>escapes</em> the
  * scan. It greps the source tree for every module declaring a {@code 
Property} or
- * {@code PassthroughPrefix} and fails if one is neither on the registry 
classpath nor an explicit
+ * {@code KeyPrefix} and fails if one is neither on the registry classpath nor 
an explicit
  * {@link #OUT_OF_SCOPE} family — so a new key-bearing parser cannot slip in 
silently.
  *
  * <p>Reads source (always present) for the declaring set and the runtime 
classpath for the scanned
@@ -53,7 +53,7 @@ public class MetadataCoverageTest {
     // Source markers for an actual key declaration (a factory call), not a 
mere type reference.
     private static final String[] DECL_MARKERS = {
             "Property.internal", "Property.external", "Property.composite",
-            "PassthroughPrefix.file(", "PassthroughPrefix.tool("};
+            "KeyPrefix.file(", "KeyPrefix.tool("};
 
     private static final String SRC_MAIN = sep("src", "main") + File.separator 
+ "java";
     private static final String SRC_TEST = sep("src", "test");
diff --git 
a/tika-metadata-schema/src/test/java/org/apache/tika/metadata/schema/MetadataSchemaTest.java
 
b/tika-metadata-schema/src/test/java/org/apache/tika/metadata/schema/MetadataSchemaTest.java
index 5530b82b10..25cf6d8aef 100644
--- 
a/tika-metadata-schema/src/test/java/org/apache/tika/metadata/schema/MetadataSchemaTest.java
+++ 
b/tika-metadata-schema/src/test/java/org/apache/tika/metadata/schema/MetadataSchemaTest.java
@@ -26,7 +26,7 @@ import org.junit.jupiter.api.Test;
 
 /**
  * The CI gate: regenerate both registries from the live {@link 
org.apache.tika.metadata.Property}
- * and {@link org.apache.tika.metadata.PassthroughPrefix} declarations and 
assert each matches its
+ * and {@link org.apache.tika.metadata.KeyPrefix} declarations and assert each 
matches its
  * committed file. If a declaration is added/changed/renamed without 
regenerating, this fails — the
  * registries cannot silently drift.
  */
@@ -44,7 +44,7 @@ public class MetadataSchemaTest {
 
     @Test
     public void committedOpenNamespacesMatchDeclarations() throws Exception {
-        // generate() first, so the classpath scan force-loads the 
PassthroughPrefix declarations.
+        // generate() first, so the classpath scan force-loads the KeyPrefix 
declarations.
         SchemaGenerator.generate();
         assertEquals(committed(OPEN), SchemaGenerator.passthroughJson(),
                 "metadata-open-namespaces.json is stale. Run 
tika-metadata-schema/regen.sh and "
diff --git 
a/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/main/java/org/apache/tika/parser/envi/EnviHeaderParser.java
 
b/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/main/java/org/apache/tika/parser/envi/EnviHeaderParser.java
index a4d902c882..19ef1f1cc4 100644
--- 
a/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/main/java/org/apache/tika/parser/envi/EnviHeaderParser.java
+++ 
b/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/main/java/org/apache/tika/parser/envi/EnviHeaderParser.java
@@ -33,8 +33,8 @@ import org.apache.tika.detect.AutoDetectReader;
 import org.apache.tika.detect.EncodingDetector;
 import org.apache.tika.exception.TikaException;
 import org.apache.tika.io.TikaInputStream;
+import org.apache.tika.metadata.KeyPrefix;
 import org.apache.tika.metadata.Metadata;
-import org.apache.tika.metadata.PassthroughPrefix;
 import org.apache.tika.mime.MediaType;
 import org.apache.tika.parser.AbstractEncodingDetectorParser;
 import org.apache.tika.parser.ParseContext;
@@ -44,8 +44,8 @@ import org.apache.tika.sax.XHTMLContentHandler;
 public class EnviHeaderParser extends AbstractEncodingDetectorParser {
 
     public static final String ENVI_MIME_TYPE = "application/envi.hdr";
-    public static final PassthroughPrefix ENVI =
-            PassthroughPrefix.file("envi.", "ENVI header field names");
+    public static final KeyPrefix ENVI =
+            KeyPrefix.file("envi.", "ENVI header field names");
     private static final long serialVersionUID = -1479368523072408091L;
     private static final Logger LOG = 
LoggerFactory.getLogger(EnviHeaderParser.class);
     private static final Set<MediaType> SUPPORTED_TYPES =
diff --git 
a/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/main/java/org/apache/tika/parser/grib/GribParser.java
 
b/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/main/java/org/apache/tika/parser/grib/GribParser.java
index 073eb7bf25..94f819f9f2 100644
--- 
a/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/main/java/org/apache/tika/parser/grib/GribParser.java
+++ 
b/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/main/java/org/apache/tika/parser/grib/GribParser.java
@@ -36,8 +36,8 @@ import org.apache.tika.annotation.TikaComponent;
 import org.apache.tika.exception.TikaException;
 import org.apache.tika.io.TikaInputStream;
 import org.apache.tika.metadata.ClimateForcast;
+import org.apache.tika.metadata.KeyPrefix;
 import org.apache.tika.metadata.Metadata;
-import org.apache.tika.metadata.PassthroughPrefix;
 import org.apache.tika.metadata.TikaCoreProperties;
 import org.apache.tika.mime.MediaType;
 import org.apache.tika.parser.ParseContext;
@@ -48,8 +48,8 @@ import org.apache.tika.sax.XHTMLContentHandler;
 public class GribParser implements Parser {
 
     public static final String GRIB_MIME_TYPE = "application/x-grib2";
-    public static final PassthroughPrefix GRIB =
-            PassthroughPrefix.file("grib:", "GRIB global attribute names");
+    public static final KeyPrefix GRIB =
+            KeyPrefix.file("grib:", "GRIB global attribute names");
     private static final long serialVersionUID = 7855458954474247655L;
     private final Set<MediaType> SUPPORTED_TYPES =
             Collections.singleton(MediaType.application("x-grib2"));
diff --git 
a/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/main/java/org/apache/tika/parser/netcdf/NetCDFParser.java
 
b/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/main/java/org/apache/tika/parser/netcdf/NetCDFParser.java
index fe2300a976..1cc2cef3e2 100644
--- 
a/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/main/java/org/apache/tika/parser/netcdf/NetCDFParser.java
+++ 
b/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/main/java/org/apache/tika/parser/netcdf/NetCDFParser.java
@@ -34,8 +34,8 @@ import org.apache.tika.annotation.TikaComponent;
 import org.apache.tika.exception.TikaException;
 import org.apache.tika.io.TikaInputStream;
 import org.apache.tika.metadata.ClimateForcast;
+import org.apache.tika.metadata.KeyPrefix;
 import org.apache.tika.metadata.Metadata;
-import org.apache.tika.metadata.PassthroughPrefix;
 import org.apache.tika.metadata.TikaCoreProperties;
 import org.apache.tika.mime.MediaType;
 import org.apache.tika.parser.ParseContext;
@@ -57,8 +57,8 @@ public class NetCDFParser implements Parser {
      */
     private static final long serialVersionUID = -5940938274907708665L;
 
-    public static final PassthroughPrefix NETCDF =
-            PassthroughPrefix.file("netcdf:", "NetCDF global attribute names");
+    public static final KeyPrefix NETCDF =
+            KeyPrefix.file("netcdf:", "NetCDF global attribute names");
 
     private final Set<MediaType> SUPPORTED_TYPES =
             Collections.singleton(MediaType.application("x-netcdf"));
diff --git 
a/tika-parsers/tika-parsers-ml/tika-parser-nlp-module/src/main/java/org/apache/tika/parser/ctakes/CTAKESContentHandler.java
 
b/tika-parsers/tika-parsers-ml/tika-parser-nlp-module/src/main/java/org/apache/tika/parser/ctakes/CTAKESContentHandler.java
index bb6ae8cdcf..9decddcad4 100644
--- 
a/tika-parsers/tika-parsers-ml/tika-parser-nlp-module/src/main/java/org/apache/tika/parser/ctakes/CTAKESContentHandler.java
+++ 
b/tika-parsers/tika-parsers-ml/tika-parser-nlp-module/src/main/java/org/apache/tika/parser/ctakes/CTAKESContentHandler.java
@@ -26,8 +26,8 @@ import org.xml.sax.ContentHandler;
 import org.xml.sax.SAXException;
 import org.xml.sax.helpers.DefaultHandler;
 
+import org.apache.tika.metadata.KeyPrefix;
 import org.apache.tika.metadata.Metadata;
-import org.apache.tika.metadata.PassthroughPrefix;
 import org.apache.tika.sax.ContentHandlerDecorator;
 
 /**
@@ -42,8 +42,8 @@ import org.apache.tika.sax.ContentHandlerDecorator;
 public class CTAKESContentHandler extends ContentHandlerDecorator {
     // Prefix used for metadata including cTAKES annotations
     public static String CTAKES_META_PREFIX = "ctakes:";
-    public static final PassthroughPrefix CTAKES =
-            PassthroughPrefix.tool(CTAKES_META_PREFIX, "cTAKES annotation type 
names");
+    public static final KeyPrefix CTAKES =
+            KeyPrefix.tool(CTAKES_META_PREFIX, "cTAKES annotation type names");
 
     // Configuration object for CTAKESContentHandler
     private CTAKESConfig config = null;
diff --git 
a/tika-parsers/tika-parsers-ml/tika-parser-nlp-module/src/main/java/org/apache/tika/parser/journal/GrobidRESTParser.java
 
b/tika-parsers/tika-parsers-ml/tika-parser-nlp-module/src/main/java/org/apache/tika/parser/journal/GrobidRESTParser.java
index 3eab5ee4de..1ad472cf1c 100644
--- 
a/tika-parsers/tika-parsers-ml/tika-parser-nlp-module/src/main/java/org/apache/tika/parser/journal/GrobidRESTParser.java
+++ 
b/tika-parsers/tika-parsers-ml/tika-parser-nlp-module/src/main/java/org/apache/tika/parser/journal/GrobidRESTParser.java
@@ -34,8 +34,8 @@ import org.xml.sax.ContentHandler;
 
 import org.apache.tika.annotation.TikaComponent;
 import org.apache.tika.exception.TikaException;
+import org.apache.tika.metadata.KeyPrefix;
 import org.apache.tika.metadata.Metadata;
-import org.apache.tika.metadata.PassthroughPrefix;
 import org.apache.tika.parser.ParseContext;
 
 /**
@@ -47,8 +47,8 @@ public class GrobidRESTParser {
 
     private static final Logger LOG = 
LoggerFactory.getLogger(GrobidRESTParser.class);
 
-    public static final PassthroughPrefix GROBID_HEADER =
-            PassthroughPrefix.tool("grobid:header_", "GROBID-inferred header 
fields");
+    public static final KeyPrefix GROBID_HEADER =
+            KeyPrefix.tool("grobid:header_", "GROBID-inferred header fields");
 
     private static final String GROBID_REST_HOST = "http://localhost:8070";;
     private static final String GROBID_ISALIVE_PATH = "/api/isalive";
diff --git 
a/tika-parsers/tika-parsers-ml/tika-parser-nlp-module/src/main/java/org/apache/tika/parser/ner/NamedEntityParser.java
 
b/tika-parsers/tika-parsers-ml/tika-parser-nlp-module/src/main/java/org/apache/tika/parser/ner/NamedEntityParser.java
index 52df871ded..9cd2f8a0d5 100644
--- 
a/tika-parsers/tika-parsers-ml/tika-parser-nlp-module/src/main/java/org/apache/tika/parser/ner/NamedEntityParser.java
+++ 
b/tika-parsers/tika-parsers-ml/tika-parser-nlp-module/src/main/java/org/apache/tika/parser/ner/NamedEntityParser.java
@@ -37,8 +37,8 @@ import org.apache.tika.annotation.TikaComponent;
 import org.apache.tika.config.loader.TikaLoader;
 import org.apache.tika.exception.TikaException;
 import org.apache.tika.io.TikaInputStream;
+import org.apache.tika.metadata.KeyPrefix;
 import org.apache.tika.metadata.Metadata;
-import org.apache.tika.metadata.PassthroughPrefix;
 import org.apache.tika.mime.MediaType;
 import org.apache.tika.parser.ParseContext;
 import org.apache.tika.parser.Parser;
@@ -62,8 +62,8 @@ public class NamedEntityParser implements Parser {
     public static final Logger LOG = 
LoggerFactory.getLogger(NamedEntityParser.class);
     public static final Set<MediaType> MEDIA_TYPES = new HashSet<>();
     public static final String MD_KEY_PREFIX = "NER_";
-    public static final PassthroughPrefix NER =
-            PassthroughPrefix.tool(MD_KEY_PREFIX, "named-entity types");
+    public static final KeyPrefix NER =
+            KeyPrefix.tool(MD_KEY_PREFIX, "named-entity types");
     public static final String DEFAULT_NER_IMPL =
             OpenNLPNERecogniser.class.getName() + "," + 
RegexNERecogniser.class.getName();
     public static final String SYS_PROP_NER_IMPL = "ner.impl.class";
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-apple-module/src/main/java/org/apache/tika/parser/iwork/NumbersContentHandler.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-apple-module/src/main/java/org/apache/tika/parser/iwork/NumbersContentHandler.java
index 87237ffca9..cb292057f1 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-apple-module/src/main/java/org/apache/tika/parser/iwork/NumbersContentHandler.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-apple-module/src/main/java/org/apache/tika/parser/iwork/NumbersContentHandler.java
@@ -23,16 +23,16 @@ import org.xml.sax.Attributes;
 import org.xml.sax.SAXException;
 import org.xml.sax.helpers.DefaultHandler;
 
+import org.apache.tika.metadata.KeyPrefix;
 import org.apache.tika.metadata.Metadata;
 import org.apache.tika.metadata.Office;
-import org.apache.tika.metadata.PassthroughPrefix;
 import org.apache.tika.metadata.TikaCoreProperties;
 import org.apache.tika.sax.XHTMLContentHandler;
 
 class NumbersContentHandler extends DefaultHandler {
 
-    private static final PassthroughPrefix NUMBERS_METADATA =
-            PassthroughPrefix.file("numbers:", "iWork Numbers document 
metadata element names");
+    private static final KeyPrefix NUMBERS_METADATA =
+            KeyPrefix.file("numbers:", "iWork Numbers document metadata 
element names");
 
     private final XHTMLContentHandler xhtml;
     private final Metadata metadata;
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-apple-module/src/main/java/org/apache/tika/parser/iwork/iwana/IWork13PackageParser.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-apple-module/src/main/java/org/apache/tika/parser/iwork/iwana/IWork13PackageParser.java
index 1ffb5fa5ec..797157e49f 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-apple-module/src/main/java/org/apache/tika/parser/iwork/iwana/IWork13PackageParser.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-apple-module/src/main/java/org/apache/tika/parser/iwork/iwana/IWork13PackageParser.java
@@ -44,8 +44,8 @@ import org.apache.tika.extractor.EmbeddedDocumentExtractor;
 import org.apache.tika.extractor.EmbeddedDocumentUtil;
 import org.apache.tika.io.TemporaryResources;
 import org.apache.tika.io.TikaInputStream;
+import org.apache.tika.metadata.KeyPrefix;
 import org.apache.tika.metadata.Metadata;
-import org.apache.tika.metadata.PassthroughPrefix;
 import org.apache.tika.metadata.Property;
 import org.apache.tika.metadata.TikaCoreProperties;
 import org.apache.tika.mime.MediaType;
@@ -67,8 +67,8 @@ public class IWork13PackageParser implements Parser {
             Property.externalText(IWORKS_PREFIX + "document-id");
     public static final Property IWORKS_BUILD_VERSION_HISTORY =
             Property.externalTextBag(IWORKS_PREFIX + "build-version-history");
-    public static final PassthroughPrefix IWORKS_PROPERTIES =
-            PassthroughPrefix.file(IWORKS_PREFIX, "iWork '13 plist document 
properties");
+    public static final KeyPrefix IWORKS_PROPERTIES =
+            KeyPrefix.file(IWORKS_PREFIX, "iWork '13 plist document 
properties");
 
 
     private final static Set<MediaType> supportedTypes = 
Collections.unmodifiableSet(
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/OggAudioParser.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/OggAudioParser.java
index 7d0b043889..f583c4fb56 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/OggAudioParser.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/ogg/OggAudioParser.java
@@ -41,8 +41,8 @@ import org.apache.tika.extractor.EmbeddedDocumentExtractor;
 import org.apache.tika.extractor.EmbeddedDocumentUtil;
 import org.apache.tika.io.TikaInputStream;
 import org.apache.tika.metadata.Audio;
+import org.apache.tika.metadata.KeyPrefix;
 import org.apache.tika.metadata.Metadata;
-import org.apache.tika.metadata.PassthroughPrefix;
 import org.apache.tika.metadata.TikaCoreProperties;
 import org.apache.tika.metadata.XMP;
 import org.apache.tika.metadata.XMPDM;
@@ -59,8 +59,8 @@ import org.apache.tika.sax.XHTMLContentHandler;
 public abstract class OggAudioParser extends AbstractParser {
     private static final long serialVersionUID = 5168743829615945633L;
 
-    private static final PassthroughPrefix VORBIS =
-            PassthroughPrefix.file("vorbis:", "Vorbis comment field names");
+    private static final KeyPrefix VORBIS =
+            KeyPrefix.file("vorbis:", "Vorbis comment field names");
 
     /**
      * Comment holding an embedded picture (e.g. cover art) as a base64
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/video/FLVParser.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/video/FLVParser.java
index 32900f319e..036362338d 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/video/FLVParser.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/video/FLVParser.java
@@ -35,8 +35,8 @@ import org.xml.sax.SAXException;
 import org.apache.tika.annotation.TikaComponent;
 import org.apache.tika.exception.TikaException;
 import org.apache.tika.io.TikaInputStream;
+import org.apache.tika.metadata.KeyPrefix;
 import org.apache.tika.metadata.Metadata;
-import org.apache.tika.metadata.PassthroughPrefix;
 import org.apache.tika.mime.MediaType;
 import org.apache.tika.parser.ParseContext;
 import org.apache.tika.parser.Parser;
@@ -77,8 +77,8 @@ public class FLVParser implements Parser {
     private static int TYPE_METADATA = 0x12;
     private static byte MASK_AUDIO = 1;
     private static byte MASK_VIDEO = 4;
-    private static final PassthroughPrefix FLV =
-            PassthroughPrefix.file("flv:", "FLV onMetaData tag keys");
+    private static final KeyPrefix FLV =
+            KeyPrefix.file("flv:", "FLV onMetaData tag keys");
 
     public Set<MediaType> getSupportedTypes(ParseContext context) {
         return SUPPORTED_TYPES;
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-cad-module/src/main/java/org/apache/tika/parser/dwg/DWGParser.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-cad-module/src/main/java/org/apache/tika/parser/dwg/DWGParser.java
index a03578d0eb..4c7eafcd03 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-cad-module/src/main/java/org/apache/tika/parser/dwg/DWGParser.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-cad-module/src/main/java/org/apache/tika/parser/dwg/DWGParser.java
@@ -31,8 +31,8 @@ import org.apache.tika.config.JsonConfig;
 import org.apache.tika.exception.TikaException;
 import org.apache.tika.io.EndianUtils;
 import org.apache.tika.io.TikaInputStream;
+import org.apache.tika.metadata.KeyPrefix;
 import org.apache.tika.metadata.Metadata;
-import org.apache.tika.metadata.PassthroughPrefix;
 import org.apache.tika.metadata.Property;
 import org.apache.tika.metadata.TikaCoreProperties;
 import org.apache.tika.mime.MediaType;
@@ -61,8 +61,8 @@ public class DWGParser extends AbstractDWGParser {
     }
 
     public static String DWG_CUSTOM_META_PREFIX = "dwg-custom:";
-    public static final PassthroughPrefix DWG_CUSTOM =
-            PassthroughPrefix.file(DWG_CUSTOM_META_PREFIX, "DWG custom 
document properties");
+    public static final KeyPrefix DWG_CUSTOM =
+            KeyPrefix.file(DWG_CUSTOM_META_PREFIX, "DWG custom document 
properties");
     /**
      * Serial version UID
      */
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/ImageMetadataExtractor.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/ImageMetadataExtractor.java
index 4008c39910..872bda41eb 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/ImageMetadataExtractor.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/ImageMetadataExtractor.java
@@ -72,8 +72,8 @@ import org.xml.sax.SAXException;
 import org.apache.tika.exception.TikaException;
 import org.apache.tika.metadata.Geographic;
 import org.apache.tika.metadata.IPTC;
+import org.apache.tika.metadata.KeyPrefix;
 import org.apache.tika.metadata.Metadata;
-import org.apache.tika.metadata.PassthroughPrefix;
 import org.apache.tika.metadata.Property;
 import org.apache.tika.metadata.TIFF;
 import org.apache.tika.metadata.TikaCoreProperties;
@@ -93,10 +93,10 @@ public class ImageMetadataExtractor {
 
     public static final String UNKNOWN_IMG_NS = "img" + 
TikaCoreProperties.NAMESPACE_PREFIX_DELIMITER;
     public static final String ICC_NS = "ICC" + 
TikaCoreProperties.NAMESPACE_PREFIX_DELIMITER;
-    public static final PassthroughPrefix UNKNOWN_IMG =
-            PassthroughPrefix.file(UNKNOWN_IMG_NS, "unrecognized image tag 
names");
-    public static final PassthroughPrefix ICC =
-            PassthroughPrefix.file(ICC_NS, "unrecognized ICC profile tag 
names");
+    public static final KeyPrefix UNKNOWN_IMG =
+            KeyPrefix.file(UNKNOWN_IMG_NS, "unrecognized image tag names");
+    public static final KeyPrefix ICC =
+            KeyPrefix.file(ICC_NS, "unrecognized ICC profile tag names");
 
     private final Metadata metadata;
     private DirectoryHandler[] handlers;
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-mail-module/src/main/java/org/apache/tika/parser/mbox/MboxParser.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-mail-module/src/main/java/org/apache/tika/parser/mbox/MboxParser.java
index 16622cbc2b..4ce712b57a 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-mail-module/src/main/java/org/apache/tika/parser/mbox/MboxParser.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-mail-module/src/main/java/org/apache/tika/parser/mbox/MboxParser.java
@@ -41,9 +41,9 @@ import org.apache.tika.exception.TikaException;
 import org.apache.tika.extractor.EmbeddedDocumentExtractor;
 import org.apache.tika.extractor.EmbeddedDocumentUtil;
 import org.apache.tika.io.TikaInputStream;
+import org.apache.tika.metadata.KeyPrefix;
 import org.apache.tika.metadata.Message;
 import org.apache.tika.metadata.Metadata;
-import org.apache.tika.metadata.PassthroughPrefix;
 import org.apache.tika.metadata.Property;
 import org.apache.tika.metadata.TikaCoreProperties;
 import org.apache.tika.mime.MediaType;
@@ -73,8 +73,8 @@ public class MboxParser implements Parser {
     private static final Pattern EMAIL_ADDRESS_PATTERN = 
Pattern.compile("<(.*@.*)>");
 
     private static final String EMAIL_HEADER_METADATA_PREFIX = "MboxParser-";
-    private static final PassthroughPrefix EMAIL_HEADER =
-            PassthroughPrefix.file(EMAIL_HEADER_METADATA_PREFIX, "mbox email 
header names");
+    private static final KeyPrefix EMAIL_HEADER =
+            KeyPrefix.file(EMAIL_HEADER_METADATA_PREFIX, "mbox email header 
names");
     private static final String EMAIL_FROMLINE_METADATA = 
EMAIL_HEADER_METADATA_PREFIX + "from";
     private final Map<Integer, Metadata> trackingMetadata = new HashMap<>();
     private boolean tracking = false;
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/JackcessParser.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/JackcessParser.java
index 1d86b6fd98..71f60ef733 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/JackcessParser.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/main/java/org/apache/tika/parser/microsoft/JackcessParser.java
@@ -34,8 +34,8 @@ import org.apache.tika.exception.EncryptedDocumentException;
 import org.apache.tika.exception.TikaException;
 import org.apache.tika.exception.UnsupportedFormatException;
 import org.apache.tika.io.TikaInputStream;
+import org.apache.tika.metadata.KeyPrefix;
 import org.apache.tika.metadata.Metadata;
-import org.apache.tika.metadata.PassthroughPrefix;
 import org.apache.tika.metadata.Property;
 import org.apache.tika.metadata.TikaCoreProperties;
 import org.apache.tika.mime.MediaType;
@@ -56,8 +56,8 @@ public class JackcessParser implements Parser {
 
     public static final String SUMMARY_PROPERTY_PREFIX =
             "mdb-summary-prop" + TikaCoreProperties.NAMESPACE_PREFIX_DELIMITER;
-    public static final PassthroughPrefix MDB_SUMMARY_PROPERTY =
-            PassthroughPrefix.file(SUMMARY_PROPERTY_PREFIX, "MS Access summary 
properties");
+    public static final KeyPrefix MDB_SUMMARY_PROPERTY =
+            KeyPrefix.file(SUMMARY_PROPERTY_PREFIX, "MS Access summary 
properties");
     private final static LinkResolver IGNORE_LINK_RESOLVER = new 
IgnoreLinkResolver();
     private static final long serialVersionUID = -752276948656079347L;
     private static final MediaType MEDIA_TYPE = 
MediaType.application("x-msaccess");
@@ -67,12 +67,12 @@ public class JackcessParser implements Parser {
     // public static Property LINKED_DATABASES = 
Property.externalTextBag("LinkedDatabases");
     public static String MDB_PROPERTY_PREFIX =
             "mdb-prop" + TikaCoreProperties.NAMESPACE_PREFIX_DELIMITER;
-    public static final PassthroughPrefix MDB_PROPERTY =
-            PassthroughPrefix.file(MDB_PROPERTY_PREFIX, "MS Access database 
properties");
+    public static final KeyPrefix MDB_PROPERTY =
+            KeyPrefix.file(MDB_PROPERTY_PREFIX, "MS Access database 
properties");
     public static String USER_DEFINED_PROPERTY_PREFIX =
             "mdb-user-prop" + TikaCoreProperties.NAMESPACE_PREFIX_DELIMITER;
-    public static final PassthroughPrefix MDB_USER_PROPERTY =
-            PassthroughPrefix.file(USER_DEFINED_PROPERTY_PREFIX, "MS Access 
user-defined properties");
+    public static final KeyPrefix MDB_USER_PROPERTY =
+            KeyPrefix.file(USER_DEFINED_PROPERTY_PREFIX, "MS Access 
user-defined properties");
     public static Property MDB_PW = Property.externalText("mdb:password");
     private Locale locale = Locale.ROOT;
 
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-webarchive-module/src/main/java/org/apache/tika/parser/warc/WARCParser.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-webarchive-module/src/main/java/org/apache/tika/parser/warc/WARCParser.java
index 7206d6c7ca..9e5198d91d 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-webarchive-module/src/main/java/org/apache/tika/parser/warc/WARCParser.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-webarchive-module/src/main/java/org/apache/tika/parser/warc/WARCParser.java
@@ -39,8 +39,8 @@ import org.apache.tika.exception.WriteLimitReachedException;
 import org.apache.tika.extractor.EmbeddedDocumentExtractor;
 import org.apache.tika.extractor.EmbeddedDocumentUtil;
 import org.apache.tika.io.TikaInputStream;
+import org.apache.tika.metadata.KeyPrefix;
 import org.apache.tika.metadata.Metadata;
-import org.apache.tika.metadata.PassthroughPrefix;
 import org.apache.tika.metadata.Property;
 import org.apache.tika.metadata.TikaCoreProperties;
 import org.apache.tika.metadata.WARC;
@@ -65,10 +65,10 @@ public class WARCParser implements Parser {
     public static String WARC_PREFIX = "warc:";
     public static String WARC_HTTP_PREFIX = WARC_PREFIX + "http:";
 
-    public static final PassthroughPrefix WARC_HEADER =
-            PassthroughPrefix.file(WARC_PREFIX, "WARC record header names");
-    public static final PassthroughPrefix WARC_HTTP_HEADER =
-            PassthroughPrefix.file(WARC_HTTP_PREFIX, "WARC HTTP response 
header names");
+    public static final KeyPrefix WARC_HEADER =
+            KeyPrefix.file(WARC_PREFIX, "WARC record header names");
+    public static final KeyPrefix WARC_HTTP_HEADER =
+            KeyPrefix.file(WARC_HTTP_PREFIX, "WARC HTTP response header 
names");
 
     public static String WARC_HTTP_STATUS = WARC_HTTP_PREFIX + "status";
 
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-xmp-commons/src/main/java/org/apache/tika/parser/xmp/XmpExtractor.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-xmp-commons/src/main/java/org/apache/tika/parser/xmp/XmpExtractor.java
index 3b96b3af0b..468f5c47cc 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-xmp-commons/src/main/java/org/apache/tika/parser/xmp/XmpExtractor.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-xmp-commons/src/main/java/org/apache/tika/parser/xmp/XmpExtractor.java
@@ -35,11 +35,11 @@ import org.xml.sax.SAXException;
 import org.apache.tika.exception.TikaException;
 import org.apache.tika.metadata.DublinCore;
 import org.apache.tika.metadata.Google;
+import org.apache.tika.metadata.KeyPrefix;
 import org.apache.tika.metadata.Metadata;
 import org.apache.tika.metadata.Office;
 import org.apache.tika.metadata.PDF;
 import org.apache.tika.metadata.PagedText;
-import org.apache.tika.metadata.PassthroughPrefix;
 import org.apache.tika.metadata.Photoshop;
 import org.apache.tika.metadata.Property;
 import org.apache.tika.metadata.TIFF;
@@ -77,7 +77,7 @@ public class XmpExtractor {
     // tk: field. Best-effort discovery surface: keys use the document's 
prefix (not the URI)
     // and are non-contractual -- promote a field into TABLE when it needs a 
stable key.
     static final String RAW_PREFIX = "xmp-raw:";
-    static final PassthroughPrefix RAW = PassthroughPrefix.file(RAW_PREFIX, 
"unmapped XMP keys");
+    static final KeyPrefix RAW = KeyPrefix.file(RAW_PREFIX, "unmapped XMP 
keys");
     // strip a trailing array index so a raw bag/seq is one multi-valued key, 
not foo:Bag[1], foo:Bag[2]
     private static final Pattern TRAILING_INDEX = 
Pattern.compile("\\[\\d+\\]$");
 

Reply via email to