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

tballison pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tika.git


The following commit(s) were added to refs/heads/main by this push:
     new 3ae604cb51 TIKA-4769 - improve metadata robustness (#2923)
3ae604cb51 is described below

commit 3ae604cb5179b0fbed71dd5ec2b6b4479f45a9ca
Author: Tim Allison <[email protected]>
AuthorDate: Wed Jul 8 08:24:50 2026 -0400

    TIKA-4769 - improve metadata robustness (#2923)
---
 .../apache/tika/digest/InputStreamDigester.java    |  12 +-
 .../java/org/apache/tika/metadata/Metadata.java    |  78 ++++++++++++-
 .../metadata/filter/CompositeMetadataFilter.java   |   2 +-
 .../tika/metadata/filter/MetadataFilter.java       |  26 ++++-
 .../tika/metadata/filter/MetadataFilterBase.java   |   3 +-
 .../apache/tika/metadata/filter/NoOpFilter.java    |   2 +-
 .../filter/RemoveByMimeMetadataFilter.java         |   2 +-
 .../org/apache/tika/parser/CompositeParser.java    |   2 +-
 .../parser/multiple/AbstractMultipleParser.java    |  10 +-
 .../java/org/apache/tika/utils/ParserUtils.java    |   7 +-
 .../metadata/MetadataInternalKeyGuardTest.java     | 125 +++++++++++++++++++++
 .../geoinfo/GeographicInformationParser.java       |   7 +-
 .../org/apache/tika/parser/grib/GribParser.java    |  27 +++--
 .../java/org/apache/tika/parser/hdf/HDFParser.java |   2 +-
 .../apache/tika/parser/netcdf/NetCDFParser.java    |  29 +++--
 .../org/apache/tika/parser/hdf/HDFParserTest.java  |   2 +-
 .../tika/parser/netcdf/NetCDFParserTest.java       |   2 +-
 .../tika/inference/AbstractEmbeddingFilter.java    |   2 +-
 .../tika/inference/JinaEmbeddingFilterTest.java    |   6 +-
 .../tika/inference/OpenAIEmbeddingFilterTest.java  |  12 +-
 .../apache/tika/parser/geo/topic/GeoParser.java    |   5 +
 .../tika/parser/journal/GrobidRESTParser.java      |   4 +
 .../tika/parser/iwork/NumbersContentHandler.java   |  32 +++---
 .../tika/parser/iwork/PagesContentHandler.java     |   2 +-
 .../org/apache/tika/parser/audio/AudioParser.java  |   1 -
 .../java/org/apache/tika/parser/mp3/Mp3Parser.java |   1 -
 .../org/apache/tika/parser/ogg/OggAudioParser.java |   4 +-
 .../org/apache/tika/parser/video/FLVParser.java    |   6 +-
 .../apache/tika/parser/audio/AudioParserTest.java  |   7 +-
 .../org/apache/tika/parser/mp3/Mp3ParserTest.java  |  18 +--
 .../apache/tika/parser/video/FLVParserTest.java    |  10 +-
 .../org/apache/tika/parser/dwg/DWGReadParser.java  |   4 +-
 .../java/org/apache/tika/parser/mat/MatParser.java |   3 +-
 .../org/apache/tika/parser/mat/MatParserTest.java  |   3 +-
 .../apache/tika/parser/font/TrueTypeParser.java    |   2 +-
 .../apache/tika/parser/font/FontParsersTest.java   |   2 +-
 .../rtf/jflex/RTFEmbeddedHandlerTest.java          |   2 +-
 .../apache/tika/parser/indesign/IDMLParser.java    |   3 +-
 .../apache/tika/parser/mif/MIFContentHandler.java  |   3 +-
 .../tika/parser/indesign/IDMLParserTest.java       |   5 +-
 .../org/apache/tika/parser/mif/MIFParserTest.java  |   5 +-
 .../tika/parser/pdf/PDMetadataExtractor.java       |  25 +++--
 .../serialization/FetchEmitTupleDeserializer.java  |   4 +-
 .../apache/tika/pipes/core/server/EmitHandler.java |  17 ++-
 .../filter/AttachmentCountingListFilter.java       |   2 +-
 .../serialization/serdes/MetadataDeserializer.java |   4 +-
 .../filter/AttachmentCountingListFilter.java       |   2 +-
 .../CustomClassSerializationTest.java              |   2 +-
 .../tika/serialization/JsonMetadataTest.java       |  22 ++++
 49 files changed, 422 insertions(+), 136 deletions(-)

diff --git 
a/tika-core/src/main/java/org/apache/tika/digest/InputStreamDigester.java 
b/tika-core/src/main/java/org/apache/tika/digest/InputStreamDigester.java
index 2d1180435b..6371d23c9e 100644
--- a/tika-core/src/main/java/org/apache/tika/digest/InputStreamDigester.java
+++ b/tika-core/src/main/java/org/apache/tika/digest/InputStreamDigester.java
@@ -20,9 +20,12 @@ import java.io.IOException;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.security.Provider;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 
 import org.apache.tika.io.TikaInputStream;
 import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.Property;
 import org.apache.tika.parser.ParseContext;
 import org.apache.tika.utils.StringUtils;
 
@@ -32,8 +35,10 @@ import org.apache.tika.utils.StringUtils;
  */
 public class InputStreamDigester implements Digester {
 
+    private static final Map<String, Property> METADATA_KEY_PROPERTIES = new 
ConcurrentHashMap<>();
+
     private final String algorithm;
-    private final String metadataKey;
+    private final Property metadataProperty;
     private final Encoder encoder;
 
     /**
@@ -45,7 +50,8 @@ public class InputStreamDigester implements Digester {
      */
     public InputStreamDigester(String algorithm, String metadataKey, Encoder 
encoder) {
         this.algorithm = algorithm;
-        this.metadataKey = metadataKey;
+        this.metadataProperty =
+                METADATA_KEY_PROPERTIES.computeIfAbsent(metadataKey, 
Property::internalText);
         this.encoder = encoder;
     }
 
@@ -107,7 +113,7 @@ public class InputStreamDigester implements Digester {
         }
 
         setContentLength(total, metadata);
-        metadata.set(metadataKey, encoder.encode(messageDigest.digest()));
+        metadata.set(metadataProperty, encoder.encode(messageDigest.digest()));
 
         tis.rewind();
     }
diff --git a/tika-core/src/main/java/org/apache/tika/metadata/Metadata.java 
b/tika-core/src/main/java/org/apache/tika/metadata/Metadata.java
index b0ede3526c..54d9c9c5a9 100644
--- a/tika-core/src/main/java/org/apache/tika/metadata/Metadata.java
+++ b/tika-core/src/main/java/org/apache/tika/metadata/Metadata.java
@@ -34,6 +34,9 @@ import java.util.Objects;
 import java.util.Properties;
 import java.util.TimeZone;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import org.apache.tika.metadata.Property.PropertyType;
 import org.apache.tika.metadata.writefilter.MetadataWriteLimiter;
 import org.apache.tika.metadata.writefilter.MetadataWriteLimiterFactory;
@@ -47,6 +50,7 @@ public class Metadata
         implements CreativeCommons, Geographic, HttpHeaders, Message, 
ClimateForcast, TIFF,
         TikaMimeKeys, Serializable {
 
+    private static final Logger LOG = LoggerFactory.getLogger(Metadata.class);
 
     private static final MetadataWriteLimiter ACCEPT_ALL = new 
MetadataWriteLimiter() {
         @Override
@@ -96,6 +100,7 @@ public class Metadata
 
 
     private MetadataWriteLimiter writeLimiter = ACCEPT_ALL;
+    private transient boolean trusted;
     /**
      * Constructs a new, empty metadata.
      */
@@ -304,9 +309,66 @@ public class Metadata
      * @param value the metadata value.
      */
     public void add(final String name, final String value) {
+        if (blockReservedKeyWrite(name)) {
+            return;
+        }
+        addUnchecked(name, value);
+    }
+
+    /** Trusted add, bypassing the reserved-key guard. */
+    private void addUnchecked(final String name, final String value) {
         writeLimiter.add(name, value, metadata);
     }
 
+    /**
+     * Mark this Metadata as a trusted transformation target (e.g. a metadata 
filter), letting
+     * String writes reach reserved {@code X-TIKA:} keys. Reset when the 
transformation is done.
+     */
+    public void setTrusted(boolean trusted) {
+        this.trusted = trusted;
+    }
+
+    public boolean isTrusted() {
+        return trusted;
+    }
+
+    /** Drop String writes to reserved {@code X-TIKA:} keys unless trusted; 
use their Property. */
+    private boolean blockReservedKeyWrite(String name) {
+        if (!trusted && name != null && 
name.startsWith(TikaCoreProperties.TIKA_META_PREFIX)) {
+            LOG.debug("Dropping String write to reserved metadata key '{}'; 
use its Property.", name);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Trusted write for clone/merge/deserialize; reserved keys go via their 
Property.
+     *
+     * @param append add rather than set
+     */
+    public void reconstruct(String name, String value, boolean append) {
+        if (name != null && 
name.startsWith(TikaCoreProperties.TIKA_META_PREFIX)) {
+            Property property = Property.get(name);
+            if (property != null) {
+                if (append) {
+                    add(property, value);
+                } else {
+                    set(property, value);
+                }
+            } else if (append) {
+                addUnchecked(name, value);
+            } else {
+                setUnchecked(name, value);
+            }
+            return;
+        }
+        if (append) {
+            add(name, value);
+        } else {
+            set(name, value);
+        }
+    }
+
     /**
      * Add a metadata name/value mapping. Add the specified value to the list 
of
      * values associated to the specified metadata name.
@@ -320,7 +382,7 @@ public class Metadata
             set(name, newValues);
         } else {
             for (String val : newValues) {
-                add(name, val);
+                addUnchecked(name, val);
             }
         }
     }
@@ -351,7 +413,7 @@ public class Metadata
                 set(property, value);
             } else {
                 if (property.isMultiValuePermitted()) {
-                    add(property.getName(), value);
+                    addUnchecked(property.getName(), value);
                 } else {
                     throw new PropertyTypeException(
                             property.getName() + " : " + 
property.getPropertyType());
@@ -384,6 +446,14 @@ public class Metadata
      * @param value the metadata value, or <code>null</code>
      */
     public void set(String name, String value) {
+        if (blockReservedKeyWrite(name)) {
+            return;
+        }
+        setUnchecked(name, value);
+    }
+
+    /** Trusted set, bypassing the reserved-key guard. */
+    private void setUnchecked(String name, String value) {
         writeLimiter.set(name, value, metadata);
     }
 
@@ -393,7 +463,7 @@ public class Metadata
         if (values != null) {
             metadata.remove(name);
             for (String v : values) {
-                add(name, v);
+                addUnchecked(name, v);
             }
         } else {
             metadata.remove(name);
@@ -419,7 +489,7 @@ public class Metadata
                 }
             }
         } else {
-            set(property.getName(), value);
+            setUnchecked(property.getName(), value);
         }
     }
 
diff --git 
a/tika-core/src/main/java/org/apache/tika/metadata/filter/CompositeMetadataFilter.java
 
b/tika-core/src/main/java/org/apache/tika/metadata/filter/CompositeMetadataFilter.java
index b5c8e2a647..e2a51f0cd4 100644
--- 
a/tika-core/src/main/java/org/apache/tika/metadata/filter/CompositeMetadataFilter.java
+++ 
b/tika-core/src/main/java/org/apache/tika/metadata/filter/CompositeMetadataFilter.java
@@ -46,7 +46,7 @@ public class CompositeMetadataFilter extends MetadataFilter {
     }
 
     @Override
-    public void filter(List<Metadata> metadataList, ParseContext parseContext) 
throws TikaException {
+    protected void doFilter(List<Metadata> metadataList, ParseContext 
parseContext) throws TikaException {
         for (MetadataFilter filter : filters) {
             filter.filter(metadataList, parseContext);
         }
diff --git 
a/tika-core/src/main/java/org/apache/tika/metadata/filter/MetadataFilter.java 
b/tika-core/src/main/java/org/apache/tika/metadata/filter/MetadataFilter.java
index 05b0a9bbb3..7b66a3f815 100644
--- 
a/tika-core/src/main/java/org/apache/tika/metadata/filter/MetadataFilter.java
+++ 
b/tika-core/src/main/java/org/apache/tika/metadata/filter/MetadataFilter.java
@@ -19,7 +19,9 @@ package org.apache.tika.metadata.filter;
 import java.io.Closeable;
 import java.io.IOException;
 import java.io.Serializable;
+import java.util.IdentityHashMap;
 import java.util.List;
+import java.util.Map;
 
 import org.apache.tika.exception.TikaException;
 import org.apache.tika.metadata.Metadata;
@@ -37,7 +39,29 @@ public abstract class MetadataFilter implements 
Serializable, Closeable {
      * @param parseContext per-request context (e.g. skip flags, runtime 
config)
      * @throws TikaException if filtering fails
      */
-    public abstract void filter(List<Metadata> metadataList, ParseContext 
parseContext)
+    public final void filter(List<Metadata> metadataList, ParseContext 
parseContext)
+            throws TikaException {
+        Map<Metadata, Boolean> previous = new IdentityHashMap<>();
+        for (Metadata m : metadataList) {
+            previous.put(m, m.isTrusted());
+            m.setTrusted(true);
+        }
+        try {
+            doFilter(metadataList, parseContext);
+        } finally {
+            previous.forEach(Metadata::setTrusted);
+        }
+    }
+
+    /**
+     * Applies the filter in place. Reserved-key String writes are permitted 
here: filters run
+     * on already-parsed, trusted metadata.
+     *
+     * @param metadataList the list to filter (must be mutable)
+     * @param parseContext per-request context
+     * @throws TikaException if filtering fails
+     */
+    protected abstract void doFilter(List<Metadata> metadataList, ParseContext 
parseContext)
             throws TikaException;
 
     /**
diff --git 
a/tika-core/src/main/java/org/apache/tika/metadata/filter/MetadataFilterBase.java
 
b/tika-core/src/main/java/org/apache/tika/metadata/filter/MetadataFilterBase.java
index 0b66d7018c..2e8dfb5b6a 100644
--- 
a/tika-core/src/main/java/org/apache/tika/metadata/filter/MetadataFilterBase.java
+++ 
b/tika-core/src/main/java/org/apache/tika/metadata/filter/MetadataFilterBase.java
@@ -30,7 +30,8 @@ import org.apache.tika.parser.ParseContext;
 public abstract class MetadataFilterBase extends MetadataFilter {
 
     @Override
-    public void filter(List<Metadata> metadataList, ParseContext parseContext) 
throws TikaException {
+    protected void doFilter(List<Metadata> metadataList, ParseContext 
parseContext)
+            throws TikaException {
         for (Metadata m : metadataList) {
             filter(m);
         }
diff --git 
a/tika-core/src/main/java/org/apache/tika/metadata/filter/NoOpFilter.java 
b/tika-core/src/main/java/org/apache/tika/metadata/filter/NoOpFilter.java
index 92ca54ad6c..7fafc2a67a 100644
--- a/tika-core/src/main/java/org/apache/tika/metadata/filter/NoOpFilter.java
+++ b/tika-core/src/main/java/org/apache/tika/metadata/filter/NoOpFilter.java
@@ -34,7 +34,7 @@ public class NoOpFilter extends MetadataFilter {
 
 
     @Override
-    public void filter(List<Metadata> metadataList, ParseContext parseContext) 
throws TikaException {
+    protected void doFilter(List<Metadata> metadataList, ParseContext 
parseContext) throws TikaException {
         // no-op
     }
 }
diff --git 
a/tika-core/src/main/java/org/apache/tika/metadata/filter/RemoveByMimeMetadataFilter.java
 
b/tika-core/src/main/java/org/apache/tika/metadata/filter/RemoveByMimeMetadataFilter.java
index 5fb930b7c8..c2b73957e9 100644
--- 
a/tika-core/src/main/java/org/apache/tika/metadata/filter/RemoveByMimeMetadataFilter.java
+++ 
b/tika-core/src/main/java/org/apache/tika/metadata/filter/RemoveByMimeMetadataFilter.java
@@ -74,7 +74,7 @@ public class RemoveByMimeMetadataFilter extends 
MetadataFilter {
     }
 
     @Override
-    public void filter(List<Metadata> metadataList, ParseContext parseContext) 
throws TikaException {
+    protected void doFilter(List<Metadata> metadataList, ParseContext 
parseContext) throws TikaException {
         metadataList.removeIf(this::shouldRemove);
     }
 
diff --git 
a/tika-core/src/main/java/org/apache/tika/parser/CompositeParser.java 
b/tika-core/src/main/java/org/apache/tika/parser/CompositeParser.java
index 4832da01c4..5e744518ba 100644
--- a/tika-core/src/main/java/org/apache/tika/parser/CompositeParser.java
+++ b/tika-core/src/main/java/org/apache/tika/parser/CompositeParser.java
@@ -345,7 +345,7 @@ public class CompositeParser implements Parser {
         for (Metadata m : record.getMetadataList()) {
             for (String n : m.names()) {
                 for (String v : m.getValues(n)) {
-                    metadata.add(n, v);
+                    metadata.reconstruct(n, v, true);
                 }
             }
         }
diff --git 
a/tika-core/src/main/java/org/apache/tika/parser/multiple/AbstractMultipleParser.java
 
b/tika-core/src/main/java/org/apache/tika/parser/multiple/AbstractMultipleParser.java
index 17eff0515e..4e10fdb398 100644
--- 
a/tika-core/src/main/java/org/apache/tika/parser/multiple/AbstractMultipleParser.java
+++ 
b/tika-core/src/main/java/org/apache/tika/parser/multiple/AbstractMultipleParser.java
@@ -125,7 +125,7 @@ public abstract class AbstractMultipleParser implements 
Parser {
             if (newVals == null || newVals.length == 0) {
                 // Metadata only in previous run, keep old values
                 for (String val : oldVals) {
-                    newMetadata.add(n, val);
+                    newMetadata.reconstruct(n, val, true);
                 }
             } else if (Arrays.deepEquals(oldVals, newVals)) {
                 // Metadata is the same, nothing to do
@@ -136,7 +136,7 @@ public abstract class AbstractMultipleParser implements 
Parser {
                         // Use the earlier value(s) in place of this/these 
one/s
                         newMetadata.remove(n);
                         for (String val : oldVals) {
-                            newMetadata.add(n, val);
+                            newMetadata.reconstruct(n, val, true);
                         }
                         continue;
                     case LAST_WINS:
@@ -147,11 +147,11 @@ public abstract class AbstractMultipleParser implements 
Parser {
                         List<String> vals = new 
ArrayList<>(Arrays.asList(oldVals));
                         newMetadata.remove(n);
                         for (String oldVal : oldVals) {
-                            newMetadata.add(n, oldVal);
+                            newMetadata.reconstruct(n, oldVal, true);
                         }
                         for (String newVal : newVals) {
                             if (!vals.contains(newVal)) {
-                                newMetadata.add(n, newVal);
+                                newMetadata.reconstruct(n, newVal, true);
                                 vals.add(newVal);
                             }
                         }
@@ -318,7 +318,7 @@ public abstract class AbstractMultipleParser implements 
Parser {
         for (String n : metadata.names()) {
             originalMetadata.remove(n);
             for (String val : metadata.getValues(n)) {
-                originalMetadata.add(n, val);
+                originalMetadata.reconstruct(n, val, true);
             }
         }
     }
diff --git a/tika-core/src/main/java/org/apache/tika/utils/ParserUtils.java 
b/tika-core/src/main/java/org/apache/tika/utils/ParserUtils.java
index 552e418859..ecd5287894 100644
--- a/tika-core/src/main/java/org/apache/tika/utils/ParserUtils.java
+++ b/tika-core/src/main/java/org/apache/tika/utils/ParserUtils.java
@@ -43,11 +43,10 @@ public class ParserUtils {
 
         for (String n : m.names()) {
             if (!m.isMultiValued(n)) {
-                clone.set(n, m.get(n));
+                clone.reconstruct(n, m.get(n), false);
             } else {
-                String[] vals = m.getValues(n);
-                for (String val : vals) {
-                    clone.add(n, val);
+                for (String val : m.getValues(n)) {
+                    clone.reconstruct(n, val, true);
                 }
             }
         }
diff --git 
a/tika-core/src/test/java/org/apache/tika/metadata/MetadataInternalKeyGuardTest.java
 
b/tika-core/src/test/java/org/apache/tika/metadata/MetadataInternalKeyGuardTest.java
new file mode 100644
index 0000000000..d6bafa5f6e
--- /dev/null
+++ 
b/tika-core/src/test/java/org/apache/tika/metadata/MetadataInternalKeyGuardTest.java
@@ -0,0 +1,125 @@
+/*
+ * 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.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import org.junit.jupiter.api.Test;
+
+/** Reserved {@code X-TIKA:} keys can't be overwritten by String writes, only 
via Property. */
+public class MetadataInternalKeyGuardTest {
+
+    @Test
+    public void testStringWriteToInternalKeyIsDropped() {
+        Metadata metadata = new Metadata();
+        // hostile scrape
+        metadata.set(TikaCoreProperties.TIKA_CONTENT.getName(), "injected");
+        assertNull(metadata.get(TikaCoreProperties.TIKA_CONTENT),
+                "String write to an internal key must be dropped");
+        assertNull(metadata.get(TikaCoreProperties.TIKA_CONTENT.getName()));
+    }
+
+    @Test
+    public void testStringAddToInternalMultiValueKeyIsDropped() {
+        Metadata metadata = new Metadata();
+        metadata.add(TikaCoreProperties.TIKA_PARSED_BY.getName(), 
"org.evil.FakeParser");
+        assertArrayEquals(new String[0], 
metadata.getValues(TikaCoreProperties.TIKA_PARSED_BY),
+                "String add to an internal key must be dropped");
+    }
+
+    @Test
+    public void testStringWriteCannotOverwriteTrustedInternalValue() {
+        Metadata metadata = new Metadata();
+        metadata.set(TikaCoreProperties.TIKA_CONTENT, "trusted");
+        // String-path attempt must not clobber
+        metadata.set(TikaCoreProperties.TIKA_CONTENT.getName(), "injected");
+        assertEquals("trusted", metadata.get(TikaCoreProperties.TIKA_CONTENT));
+    }
+
+    @Test
+    public void testPropertyWriteToInternalKeyStillWorks() {
+        Metadata metadata = new Metadata();
+        metadata.set(TikaCoreProperties.TIKA_CONTENT, "legit");
+        assertEquals("legit", metadata.get(TikaCoreProperties.TIKA_CONTENT));
+
+        metadata.add(TikaCoreProperties.TIKA_PARSED_BY, "p1");
+        metadata.add(TikaCoreProperties.TIKA_PARSED_BY, "p2");
+        assertArrayEquals(new String[] {"p1", "p2"},
+                metadata.getValues(TikaCoreProperties.TIKA_PARSED_BY));
+    }
+
+    @Test
+    public void testNonReservedStringKeysAreUnaffected() {
+        Metadata metadata = new Metadata();
+        // unregistered key passes through
+        metadata.set("my:customKey", "value");
+        assertEquals("value", metadata.get("my:customKey"));
+
+        // a registered non-reserved property (dc:description) written by name 
still works
+        metadata.set(TikaCoreProperties.DESCRIPTION.getName(), "hello");
+        assertEquals("hello", metadata.get(TikaCoreProperties.DESCRIPTION));
+    }
+
+    @Test
+    public void testNullNameDoesNotThrow() {
+        Metadata metadata = new Metadata();
+        // no NPE on a null name
+        metadata.set((String) null, "x");
+    }
+
+    @Test
+    public void testReconstructPreservesRegisteredReservedKey() {
+        Metadata metadata = new Metadata();
+        metadata.reconstruct(TikaCoreProperties.TIKA_CONTENT.getName(), "the 
content", false);
+        assertEquals("the content", 
metadata.get(TikaCoreProperties.TIKA_CONTENT));
+
+        metadata.reconstruct(TikaCoreProperties.TIKA_PARSED_BY.getName(), 
"p1", true);
+        metadata.reconstruct(TikaCoreProperties.TIKA_PARSED_BY.getName(), 
"p2", true);
+        assertArrayEquals(new String[] {"p1", "p2"},
+                metadata.getValues(TikaCoreProperties.TIKA_PARSED_BY));
+    }
+
+    @Test
+    public void testReconstructPreservesUnregisteredReservedKey() {
+        Metadata metadata = new Metadata();
+        String unregistered = TikaCoreProperties.TIKA_META_PREFIX + 
"noSuchRegisteredProperty";
+        assertNull(Property.get(unregistered), "precondition: key must be 
unregistered");
+
+        metadata.set(unregistered, "dropped");
+        assertNull(metadata.get(unregistered));
+
+        metadata.reconstruct(unregistered, "kept", false);
+        assertEquals("kept", metadata.get(unregistered));
+
+        metadata.reconstruct(unregistered, "kept2", true);
+        assertArrayEquals(new String[] {"kept", "kept2"}, 
metadata.getValues(unregistered));
+    }
+
+    @Test
+    public void testTrustedModeAllowsReservedStringWrites() {
+        Metadata metadata = new Metadata();
+        metadata.setTrusted(true);
+        metadata.set(TikaCoreProperties.TIKA_CONTENT.getName(), "trusted");
+        assertEquals("trusted", metadata.get(TikaCoreProperties.TIKA_CONTENT));
+
+        metadata.setTrusted(false);
+        metadata.set(TikaCoreProperties.TIKA_CONTENT.getName(), "blocked");
+        assertEquals("trusted", metadata.get(TikaCoreProperties.TIKA_CONTENT));
+    }
+}
diff --git 
a/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/main/java/org/apache/tika/parser/geoinfo/GeographicInformationParser.java
 
b/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/main/java/org/apache/tika/parser/geoinfo/GeographicInformationParser.java
index b385955d8e..ebd964b8f0 100644
--- 
a/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/main/java/org/apache/tika/parser/geoinfo/GeographicInformationParser.java
+++ 
b/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/main/java/org/apache/tika/parser/geoinfo/GeographicInformationParser.java
@@ -64,6 +64,7 @@ import org.apache.tika.exception.TikaException;
 import org.apache.tika.io.TemporaryResources;
 import org.apache.tika.io.TikaInputStream;
 import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.TikaCoreProperties;
 import org.apache.tika.mime.MediaType;
 import org.apache.tika.parser.ParseContext;
 import org.apache.tika.parser.Parser;
@@ -234,7 +235,7 @@ public class GeographicInformationParser implements Parser {
         for (Identification i : identifications) {
             DefaultDataIdentification defaultDataIdentification = 
(DefaultDataIdentification) i;
             if (i.getCitation() != null && i.getCitation().getTitle() != null) 
{
-                metadata.add("IdentificationInfoCitationTitle ",
+                metadata.set(TikaCoreProperties.TITLE,
                         i.getCitation().getTitle().toString());
             }
 
@@ -271,7 +272,7 @@ public class GeographicInformationParser implements Parser {
                 }
             }
             if (i.getAbstract() != null) {
-                metadata.add("IdentificationInfoAbstract ", 
i.getAbstract().toString());
+                metadata.set(TikaCoreProperties.DESCRIPTION, 
i.getAbstract().toString());
             }
             for (Progress p : i.getStatus()) {
                 metadata.add("IdentificationInfoStatus ", p.name());
@@ -286,7 +287,7 @@ public class GeographicInformationParser implements Parser {
             Map<Locale, Charset> localeCharsetMap =
                     defaultDataIdentification.getLocalesAndCharsets();
             for (Locale l : localeCharsetMap.keySet()) {
-                metadata.add("IdentificationInfoLanguage-->", 
l.getDisplayLanguage(Locale.ENGLISH));
+                metadata.set(TikaCoreProperties.LANGUAGE, 
l.getDisplayLanguage(Locale.ENGLISH));
             }
             CodeListSet<TopicCategory> categoryList =
                     (CodeListSet<TopicCategory>) 
defaultDataIdentification.getTopicCategories();
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 94f8d77911..1f98243e06 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
@@ -35,8 +35,8 @@ import ucar.nc2.dataset.NetcdfDataset;
 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.Metadata;
-import org.apache.tika.metadata.Property;
 import org.apache.tika.metadata.TikaCoreProperties;
 import org.apache.tika.mime.MediaType;
 import org.apache.tika.parser.ParseContext;
@@ -74,12 +74,11 @@ public class GribParser implements Parser {
 
                 // first parse out the set of global attributes
                 for (Attribute attr : ncFile.getGlobalAttributes()) {
-                    Property property = resolveMetadataKey(attr.getFullName());
                     if (attr.getDataType().isString()) {
-                        metadata.add(property, attr.getStringValue());
+                        addGlobalAttribute(metadata, attr.getFullName(), 
attr.getStringValue());
                     } else if (attr.getDataType().isNumeric()) {
-                        int value = attr.getNumericValue().intValue();
-                        metadata.add(property, String.valueOf(value));
+                        addGlobalAttribute(metadata, attr.getFullName(),
+                                
String.valueOf(attr.getNumericValue().intValue()));
                     }
                 }
 
@@ -122,11 +121,21 @@ public class GribParser implements Parser {
         }
     }
 
-    private Property resolveMetadataKey(String localName) {
-        if ("title".equals(localName)) {
-            return TikaCoreProperties.TITLE;
+    private static final Set<String> CF_GLOBAL_ATTRIBUTES = Set.of(
+            ClimateForcast.PROGRAM_ID, ClimateForcast.COMMAND_LINE, 
ClimateForcast.HISTORY,
+            ClimateForcast.TABLE_ID, ClimateForcast.INSTITUTION, 
ClimateForcast.SOURCE,
+            ClimateForcast.CONTACT, ClimateForcast.PROJECT_ID, 
ClimateForcast.CONVENTIONS,
+            ClimateForcast.REFERENCES, ClimateForcast.ACKNOWLEDGEMENT, 
ClimateForcast.REALIZATION,
+            ClimateForcast.EXPERIMENT_ID, ClimateForcast.COMMENT, 
ClimateForcast.MODEL_NAME_ENGLISH);
+
+    private static void addGlobalAttribute(Metadata metadata, String name, 
String value) {
+        if ("title".equals(name)) {
+            metadata.add(TikaCoreProperties.TITLE, value);
+        } else if (CF_GLOBAL_ATTRIBUTES.contains(name)) {
+            metadata.add(name, value);
+        } else {
+            metadata.add("grib:" + name, value);
         }
-        return Property.internalText(localName);
     }
 
 }
diff --git 
a/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/main/java/org/apache/tika/parser/hdf/HDFParser.java
 
b/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/main/java/org/apache/tika/parser/hdf/HDFParser.java
index d3f7c36d85..673f5485fc 100644
--- 
a/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/main/java/org/apache/tika/parser/hdf/HDFParser.java
+++ 
b/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/main/java/org/apache/tika/parser/hdf/HDFParser.java
@@ -105,7 +105,7 @@ public class HDFParser implements Parser {
         }
 
         // get file type
-        met.set("File-Type-Description", ncFile.getFileTypeDescription());
+        met.set("hdf:File-Type-Description", ncFile.getFileTypeDescription());
         // unravel its string attrs
         for (Attribute attribute : group.getAttributes()) {
             if (attribute.isString()) {
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 364a3a5d83..f40f31215c 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
@@ -33,8 +33,8 @@ import ucar.nc2.Variable;
 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.Metadata;
-import org.apache.tika.metadata.Property;
 import org.apache.tika.metadata.TikaCoreProperties;
 import org.apache.tika.mime.MediaType;
 import org.apache.tika.parser.ParseContext;
@@ -81,15 +81,14 @@ public class NetCDFParser implements Parser {
                       ParseContext context) throws IOException, SAXException, 
TikaException {
 
         try (NetcdfFile ncFile = 
NetcdfFile.open(tis.getFile().getAbsolutePath())) {
-            metadata.set("File-Type-Description", 
ncFile.getFileTypeDescription());
+            metadata.set("netcdf:File-Type-Description", 
ncFile.getFileTypeDescription());
             // first parse out the set of global attributes
             for (Attribute attr : ncFile.getGlobalAttributes()) {
-                Property property = resolveMetadataKey(attr.getFullName());
                 if (attr.getDataType().isString()) {
-                    metadata.add(property, attr.getStringValue());
+                    addGlobalAttribute(metadata, attr.getFullName(), 
attr.getStringValue());
                 } else if (attr.getDataType().isNumeric()) {
-                    int value = attr.getNumericValue().intValue();
-                    metadata.add(property, String.valueOf(value));
+                    addGlobalAttribute(metadata, attr.getFullName(),
+                            String.valueOf(attr.getNumericValue().intValue()));
                 }
             }
 
@@ -130,10 +129,20 @@ public class NetCDFParser implements Parser {
         }
     }
 
-    private Property resolveMetadataKey(String localName) {
-        if ("title".equals(localName)) {
-            return TikaCoreProperties.TITLE;
+    private static final Set<String> CF_GLOBAL_ATTRIBUTES = Set.of(
+            ClimateForcast.PROGRAM_ID, ClimateForcast.COMMAND_LINE, 
ClimateForcast.HISTORY,
+            ClimateForcast.TABLE_ID, ClimateForcast.INSTITUTION, 
ClimateForcast.SOURCE,
+            ClimateForcast.CONTACT, ClimateForcast.PROJECT_ID, 
ClimateForcast.CONVENTIONS,
+            ClimateForcast.REFERENCES, ClimateForcast.ACKNOWLEDGEMENT, 
ClimateForcast.REALIZATION,
+            ClimateForcast.EXPERIMENT_ID, ClimateForcast.COMMENT, 
ClimateForcast.MODEL_NAME_ENGLISH);
+
+    private static void addGlobalAttribute(Metadata metadata, String name, 
String value) {
+        if ("title".equals(name)) {
+            metadata.add(TikaCoreProperties.TITLE, value);
+        } else if (CF_GLOBAL_ATTRIBUTES.contains(name)) {
+            metadata.add(name, value);
+        } else {
+            metadata.add("netcdf:" + name, value);
         }
-        return Property.internalText(localName);
     }
 }
diff --git 
a/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/test/java/org/apache/tika/parser/hdf/HDFParserTest.java
 
b/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/test/java/org/apache/tika/parser/hdf/HDFParserTest.java
index 5f5f2c16c8..69f61863df 100644
--- 
a/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/test/java/org/apache/tika/parser/hdf/HDFParserTest.java
+++ 
b/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/test/java/org/apache/tika/parser/hdf/HDFParserTest.java
@@ -79,6 +79,6 @@ public class HDFParserTest extends TikaTest {
         assertNotNull(metadata);
         assertEquals("Direct read of HDF4 file through CDM library", 
metadata.get("_History"));
         assertEquals("Ascending", metadata.get("Pass"));
-        assertEquals("Hierarchical Data Format, version 4", 
metadata.get("File-Type-Description"));
+        assertEquals("Hierarchical Data Format, version 4", 
metadata.get("hdf:File-Type-Description"));
     }
 }
diff --git 
a/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/test/java/org/apache/tika/parser/netcdf/NetCDFParserTest.java
 
b/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/test/java/org/apache/tika/parser/netcdf/NetCDFParserTest.java
index 2e04296c80..735bbd5a38 100644
--- 
a/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/test/java/org/apache/tika/parser/netcdf/NetCDFParserTest.java
+++ 
b/tika-parsers/tika-parsers-extended/tika-parser-scientific-module/src/test/java/org/apache/tika/parser/netcdf/NetCDFParserTest.java
@@ -52,7 +52,7 @@ public class NetCDFParserTest {
         assertEquals(metadata.get(Metadata.REALIZATION), "1");
         assertEquals(metadata.get(Metadata.EXPERIMENT_ID),
                 "720 ppm stabilization experiment (SRESA1B)");
-        assertEquals(metadata.get("File-Type-Description"), "NetCDF-3/CDM");
+        assertEquals(metadata.get("netcdf:File-Type-Description"), 
"NetCDF-3/CDM");
 
         String content = handler.toString();
         assertContains("long_name = \"Surface area\"", content);
diff --git 
a/tika-parsers/tika-parsers-ml/tika-inference/src/main/java/org/apache/tika/inference/AbstractEmbeddingFilter.java
 
b/tika-parsers/tika-parsers-ml/tika-inference/src/main/java/org/apache/tika/inference/AbstractEmbeddingFilter.java
index a89573c29b..4dffd47929 100644
--- 
a/tika-parsers/tika-parsers-ml/tika-inference/src/main/java/org/apache/tika/inference/AbstractEmbeddingFilter.java
+++ 
b/tika-parsers/tika-parsers-ml/tika-inference/src/main/java/org/apache/tika/inference/AbstractEmbeddingFilter.java
@@ -86,7 +86,7 @@ public abstract class AbstractEmbeddingFilter extends 
MetadataFilter {
             throws IOException, TikaException;
 
     @Override
-    public void filter(List<Metadata> metadataList, ParseContext parseContext) 
throws TikaException {
+    protected void doFilter(List<Metadata> metadataList, ParseContext 
parseContext) throws TikaException {
         InferenceConfig requestConfig = 
parseContext.get(InferenceConfig.class);
         if (requestConfig != null && requestConfig.isSkipEmbedding()) {
             return;
diff --git 
a/tika-parsers/tika-parsers-ml/tika-inference/src/test/java/org/apache/tika/inference/JinaEmbeddingFilterTest.java
 
b/tika-parsers/tika-parsers-ml/tika-inference/src/test/java/org/apache/tika/inference/JinaEmbeddingFilterTest.java
index 803eb48523..ae25f3915a 100644
--- 
a/tika-parsers/tika-parsers-ml/tika-inference/src/test/java/org/apache/tika/inference/JinaEmbeddingFilterTest.java
+++ 
b/tika-parsers/tika-parsers-ml/tika-inference/src/test/java/org/apache/tika/inference/JinaEmbeddingFilterTest.java
@@ -66,7 +66,7 @@ public class JinaEmbeddingFilterTest {
                 buildEmbeddingResponse(1, 3)));
 
         Metadata metadata = new Metadata();
-        metadata.set(TikaCoreProperties.TIKA_CONTENT.getName(), "Some document 
text.");
+        metadata.set(TikaCoreProperties.TIKA_CONTENT, "Some document text.");
         List<Metadata> list = new ArrayList<>();
         list.add(metadata);
         filter.filter(list);
@@ -85,7 +85,7 @@ public class JinaEmbeddingFilterTest {
                 buildEmbeddingResponse(1, 3)));
 
         Metadata metadata = new Metadata();
-        metadata.set(TikaCoreProperties.TIKA_CONTENT.getName(), "What is 
Tika?");
+        metadata.set(TikaCoreProperties.TIKA_CONTENT, "What is Tika?");
         List<Metadata> list = new ArrayList<>();
         list.add(metadata);
         filter.filter(list);
@@ -119,7 +119,7 @@ public class JinaEmbeddingFilterTest {
                 + "# Background\n\nSecond section text.";
 
         Metadata metadata = new Metadata();
-        metadata.set(TikaCoreProperties.TIKA_CONTENT.getName(), content);
+        metadata.set(TikaCoreProperties.TIKA_CONTENT, content);
         List<Metadata> list = new ArrayList<>();
         list.add(metadata);
         filter.filter(list);
diff --git 
a/tika-parsers/tika-parsers-ml/tika-inference/src/test/java/org/apache/tika/inference/OpenAIEmbeddingFilterTest.java
 
b/tika-parsers/tika-parsers-ml/tika-inference/src/test/java/org/apache/tika/inference/OpenAIEmbeddingFilterTest.java
index 3c58a21b71..cda29abe2d 100644
--- 
a/tika-parsers/tika-parsers-ml/tika-inference/src/test/java/org/apache/tika/inference/OpenAIEmbeddingFilterTest.java
+++ 
b/tika-parsers/tika-parsers-ml/tika-inference/src/test/java/org/apache/tika/inference/OpenAIEmbeddingFilterTest.java
@@ -72,7 +72,7 @@ public class OpenAIEmbeddingFilterTest {
                 buildEmbeddingResponse(2, 3)));
 
         Metadata metadata = new Metadata();
-        metadata.set(TikaCoreProperties.TIKA_CONTENT.getName(), content);
+        metadata.set(TikaCoreProperties.TIKA_CONTENT, content);
 
         List<Metadata> metadataList = new ArrayList<>();
         metadataList.add(metadata);
@@ -107,7 +107,7 @@ public class OpenAIEmbeddingFilterTest {
                 buildEmbeddingResponse(1, 3)));
 
         Metadata metadata = new Metadata();
-        metadata.set(TikaCoreProperties.TIKA_CONTENT.getName(), "Some text.");
+        metadata.set(TikaCoreProperties.TIKA_CONTENT, "Some text.");
         List<Metadata> list = new ArrayList<>();
         list.add(metadata);
         filter.filter(list);
@@ -131,7 +131,7 @@ public class OpenAIEmbeddingFilterTest {
     @Test
     void testBlankContent() throws Exception {
         Metadata metadata = new Metadata();
-        metadata.set(TikaCoreProperties.TIKA_CONTENT.getName(), "   ");
+        metadata.set(TikaCoreProperties.TIKA_CONTENT, "   ");
         List<Metadata> list = new ArrayList<>();
         list.add(metadata);
         filter.filter(list);
@@ -145,7 +145,7 @@ public class OpenAIEmbeddingFilterTest {
         server.enqueue(new TikaTestHttpServer.MockResponse(500, 
"{\"error\":\"boom\"}"));
 
         Metadata metadata = new Metadata();
-        metadata.set(TikaCoreProperties.TIKA_CONTENT.getName(), "Some text.");
+        metadata.set(TikaCoreProperties.TIKA_CONTENT, "Some text.");
         List<Metadata> list = new ArrayList<>();
         list.add(metadata);
 
@@ -183,7 +183,7 @@ public class OpenAIEmbeddingFilterTest {
                 buildEmbeddingResponse(1, 3)));
 
         Metadata metadata = new Metadata();
-        metadata.set(TikaCoreProperties.TIKA_CONTENT.getName(), "Single chunk 
of text.");
+        metadata.set(TikaCoreProperties.TIKA_CONTENT, "Single chunk of text.");
         List<Metadata> list = new ArrayList<>();
         list.add(metadata);
         filter.filter(list);
@@ -205,7 +205,7 @@ public class OpenAIEmbeddingFilterTest {
                 buildEmbeddingResponse(1, 3)));
 
         Metadata metadata = new Metadata();
-        metadata.set(TikaCoreProperties.TIKA_CONTENT.getName(), "Some text.");
+        metadata.set(TikaCoreProperties.TIKA_CONTENT, "Some text.");
 
         // Pre-populate with an image chunk
         Chunk imgChunk = new Chunk(null,
diff --git 
a/tika-parsers/tika-parsers-ml/tika-parser-nlp-module/src/main/java/org/apache/tika/parser/geo/topic/GeoParser.java
 
b/tika-parsers/tika-parsers-ml/tika-parser-nlp-module/src/main/java/org/apache/tika/parser/geo/topic/GeoParser.java
index 45556c927a..73efd34d49 100644
--- 
a/tika-parsers/tika-parsers-ml/tika-parser-nlp-module/src/main/java/org/apache/tika/parser/geo/topic/GeoParser.java
+++ 
b/tika-parsers/tika-parsers-ml/tika-parser-nlp-module/src/main/java/org/apache/tika/parser/geo/topic/GeoParser.java
@@ -44,6 +44,11 @@ import org.apache.tika.parser.Parser;
 import org.apache.tika.parser.geo.topic.gazetteer.GeoGazetteerClient;
 import org.apache.tika.parser.geo.topic.gazetteer.Location;
 
+/**
+ * Locations here are inferred from the text (NER + gazetteer), so they are 
stored under
+ * {@code Geographic_*} keys rather than the standard {@code Geographic} 
coordinate properties, which
+ * denote coordinates the file asserts about itself.
+ */
 @TikaComponent
 public class GeoParser implements Parser {
     private static final long serialVersionUID = -2241391757440215491L;
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 26461a8282..f6c761ac40 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
@@ -37,6 +37,10 @@ import org.apache.tika.exception.TikaException;
 import org.apache.tika.metadata.Metadata;
 import org.apache.tika.parser.ParseContext;
 
+/**
+ * GROBID header fields are namespaced under {@code grobid:header_*} rather 
than mapped to standard
+ * properties (e.g. {@code dc:title}): they are model inferences, not what the 
file asserts about itself.
+ */
 @TikaComponent
 public class GrobidRESTParser {
 
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 bda113df79..bb5ffa52a0 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
@@ -25,7 +25,6 @@ import org.xml.sax.helpers.DefaultHandler;
 
 import org.apache.tika.metadata.Metadata;
 import org.apache.tika.metadata.Office;
-import org.apache.tika.metadata.Property;
 import org.apache.tika.metadata.TikaCoreProperties;
 import org.apache.tika.sax.XHTMLContentHandler;
 
@@ -40,7 +39,7 @@ class NumbersContentHandler extends DefaultHandler {
     private boolean parseText = false;
 
     private boolean inMetadata = false;
-    private Property metadataKey;
+    private String metadataLocalName;
     private String metadataPropertyQName;
 
     private boolean inTable = false;
@@ -69,7 +68,7 @@ class NumbersContentHandler extends DefaultHandler {
             numberOfSheets++;
             xhtml.startElement("div");
             String sheetName = attributes.getValue("ls:workspace-name");
-            metadata.add("sheetNames", sheetName);
+            metadata.add("numbers:sheetNames", sheetName);
         }
 
         if ("sf:text".equals(qName)) {
@@ -86,13 +85,13 @@ class NumbersContentHandler extends DefaultHandler {
             return;
         }
 
-        if (inMetadata && metadataKey == null) {
-            metadataKey = resolveMetadataKey(localName);
+        if (inMetadata && metadataLocalName == null) {
+            metadataLocalName = localName;
             metadataPropertyQName = qName;
         }
 
-        if (inMetadata && metadataKey != null && "sf:string".equals(qName)) {
-            metadata.add(metadataKey, attributes.getValue("sfa:string"));
+        if (inMetadata && metadataLocalName != null && 
"sf:string".equals(qName)) {
+            addMetadata(metadata, metadataLocalName, 
attributes.getValue("sfa:string"));
         }
 
         if (!inSheet) {
@@ -198,7 +197,7 @@ class NumbersContentHandler extends DefaultHandler {
 
         if (inMetadata && qName.equals(metadataPropertyQName)) {
             metadataPropertyQName = null;
-            metadataKey = null;
+            metadataLocalName = null;
         }
 
         if (!inSheet) {
@@ -219,16 +218,15 @@ class NumbersContentHandler extends DefaultHandler {
         }
     }
 
-    private Property resolveMetadataKey(String localName) {
+    private static void addMetadata(Metadata metadata, String localName, 
String value) {
         if ("authors".equals(localName)) {
-            return TikaCoreProperties.CREATOR;
+            metadata.add(TikaCoreProperties.CREATOR, value);
+        } else if ("title".equals(localName)) {
+            metadata.add(TikaCoreProperties.TITLE, value);
+        } else if ("comment".equals(localName)) {
+            metadata.add(TikaCoreProperties.COMMENTS, value);
+        } else {
+            metadata.add("numbers:" + localName, value);
         }
-        if ("title".equals(localName)) {
-            return TikaCoreProperties.TITLE;
-        }
-        if ("comment".equals(localName)) {
-            return TikaCoreProperties.COMMENTS;
-        }
-        return Property.internalText(localName);
     }
 }
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-apple-module/src/main/java/org/apache/tika/parser/iwork/PagesContentHandler.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-apple-module/src/main/java/org/apache/tika/parser/iwork/PagesContentHandler.java
index 2298983d1f..dd726ee27a 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-apple-module/src/main/java/org/apache/tika/parser/iwork/PagesContentHandler.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-apple-module/src/main/java/org/apache/tika/parser/iwork/PagesContentHandler.java
@@ -293,7 +293,7 @@ class PagesContentHandler extends DefaultHandler {
      * @return a resolved key that is common in other document types
      */
     private Object resolveMetaDataKey(String metaDataLocalName) {
-        Object metaDataKey = metaDataLocalName;
+        Object metaDataKey = "pages:" + metaDataLocalName;
         if ("sf:authors".equals(metaDataQName)) {
             metaDataKey = TikaCoreProperties.CREATOR;
         } else if ("sf:title".equals(metaDataQName)) {
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/audio/AudioParser.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/audio/AudioParser.java
index da1a0e9a39..543c54fe73 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/audio/AudioParser.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/audio/AudioParser.java
@@ -92,7 +92,6 @@ public class AudioParser implements Parser {
             }
             float rate = audioFormat.getSampleRate();
             if (rate != AudioSystem.NOT_SPECIFIED) {
-                metadata.set("samplerate", String.valueOf(rate));
                 metadata.set(XMPDM.AUDIO_SAMPLE_RATE, Integer.toString((int) 
rate));
             }
             int bits = audioFormat.getSampleSizeInBits();
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp3/Mp3Parser.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp3/Mp3Parser.java
index 7f23209b0f..0fedb73b35 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp3/Mp3Parser.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/main/java/org/apache/tika/parser/mp3/Mp3Parser.java
@@ -158,7 +158,6 @@ public class Mp3Parser implements Parser {
         }
 
         if (audioAndTags.audio != null) {
-            metadata.set("samplerate", 
String.valueOf(audioAndTags.audio.getSampleRate()));
             metadata.set("channels", 
String.valueOf(audioAndTags.audio.getChannels()));
             metadata.set("version", audioAndTags.audio.getVersion());
 
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 563bf40bed..31af6758d4 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
@@ -78,7 +78,7 @@ public abstract class OggAudioParser extends AbstractParser {
         metadata.set(XMPDM.GENRE, comments.getGenre());
         metadata.set(XMPDM.RELEASE_DATE, comments.getDate());
         metadata.add(XMP.CREATOR_TOOL, comments.getVendor());
-        metadata.add("vendor", comments.getVendor());
+        metadata.add("vorbis:vendor", comments.getVendor());
 
         for (String comment : comments.getComments("comment")) {
             metadata.add(XMPDM.LOG_COMMENT.getName(), comment);
@@ -94,7 +94,7 @@ public abstract class OggAudioParser extends AbstractParser {
         for (String key : comments.getAllComments().keySet()) {
             if (!done.contains(key)) {
                 for (String value : comments.getAllComments().get(key)) {
-                    metadata.add(key, value);
+                    metadata.add("vorbis:" + key, value);
                 }
             }
         }
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 fdbf7c06fb..a3c36ebeea 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
@@ -195,8 +195,8 @@ public class FLVParser implements Parser {
         }
 
         metadata.set(Metadata.CONTENT_TYPE, "video/x-flv");
-        metadata.set("hasVideo", Boolean.toString((typeFlags & MASK_VIDEO) != 
0));
-        metadata.set("hasAudio", Boolean.toString((typeFlags & MASK_AUDIO) != 
0));
+        metadata.set("flv:hasVideo", Boolean.toString((typeFlags & MASK_VIDEO) 
!= 0));
+        metadata.set("flv:hasAudio", Boolean.toString((typeFlags & MASK_AUDIO) 
!= 0));
 
         XHTMLContentHandler xhtml = new XHTMLContentHandler(handler, metadata, 
context);
         xhtml.startDocument();
@@ -245,7 +245,7 @@ public class FLVParser implements Parser {
                             if (entry.getValue() == null) {
                                 continue;
                             }
-                            metadata.set(entry.getKey(), 
entry.getValue().toString());
+                            metadata.set("flv:" + entry.getKey(), 
entry.getValue().toString());
                         }
                     }
                 }
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/audio/AudioParserTest.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/audio/AudioParserTest.java
index 12f753356d..d6e40c1dfd 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/audio/AudioParserTest.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/audio/AudioParserTest.java
@@ -22,6 +22,7 @@ import org.junit.jupiter.api.Test;
 
 import org.apache.tika.Tika;
 import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.XMPDM;
 
 public class AudioParserTest {
 
@@ -33,7 +34,7 @@ public class AudioParserTest {
                 new 
Tika().parseToString(AudioParserTest.class.getResourceAsStream(path), metadata);
 
         assertEquals("audio/vnd.wave", metadata.get(Metadata.CONTENT_TYPE));
-        assertEquals("44100.0", metadata.get("samplerate"));
+        assertEquals("44100", metadata.get(XMPDM.AUDIO_SAMPLE_RATE));
         assertEquals("2", metadata.get("channels"));
         assertEquals("16", metadata.get("bits"));
         assertEquals("PCM_SIGNED", metadata.get("encoding"));
@@ -49,7 +50,7 @@ public class AudioParserTest {
                 new 
Tika().parseToString(AudioParserTest.class.getResourceAsStream(path), metadata);
 
         assertEquals("audio/x-aiff", metadata.get(Metadata.CONTENT_TYPE));
-        assertEquals("44100.0", metadata.get("samplerate"));
+        assertEquals("44100", metadata.get(XMPDM.AUDIO_SAMPLE_RATE));
         assertEquals("2", metadata.get("channels"));
         assertEquals("16", metadata.get("bits"));
         assertEquals("PCM_SIGNED", metadata.get("encoding"));
@@ -65,7 +66,7 @@ public class AudioParserTest {
                 new 
Tika().parseToString(AudioParserTest.class.getResourceAsStream(path), metadata);
 
         assertEquals("audio/basic", metadata.get(Metadata.CONTENT_TYPE));
-        assertEquals("44100.0", metadata.get("samplerate"));
+        assertEquals("44100", metadata.get(XMPDM.AUDIO_SAMPLE_RATE));
         assertEquals("2", metadata.get("channels"));
         assertEquals("16", metadata.get("bits"));
         assertEquals("PCM_SIGNED", metadata.get("encoding"));
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/mp3/Mp3ParserTest.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/mp3/Mp3ParserTest.java
index 40a9870408..c5d098ffc7 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/mp3/Mp3ParserTest.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/mp3/Mp3ParserTest.java
@@ -65,7 +65,7 @@ public class Mp3ParserTest extends TikaTest {
         assertContains("Rock", content);
 
         assertEquals("MPEG 3 Layer III Version 1", metadata.get("version"));
-        assertEquals("44100", metadata.get("samplerate"));
+        assertEquals("44100", metadata.get(XMPDM.AUDIO_SAMPLE_RATE));
         assertEquals("1", metadata.get("channels"));
         checkDuration(metadata, 2);
     }
@@ -96,7 +96,7 @@ public class Mp3ParserTest extends TikaTest {
 
         // Check un-typed audio properties
         assertEquals("MPEG 3 Layer III Version 1", metadata.get("version"));
-        assertEquals("44100", metadata.get("samplerate"));
+        assertEquals("44100", metadata.get(XMPDM.AUDIO_SAMPLE_RATE));
         assertEquals("1", metadata.get("channels"));
 
         // Check XMPDM-typed audio properties
@@ -151,7 +151,7 @@ public class Mp3ParserTest extends TikaTest {
         assertContains("Rock", content);
 
         assertEquals("MPEG 3 Layer III Version 1", metadata.get("version"));
-        assertEquals("44100", metadata.get("samplerate"));
+        assertEquals("44100", metadata.get(XMPDM.AUDIO_SAMPLE_RATE));
         assertEquals("1", metadata.get("channels"));
         checkDuration(metadata, 2);
     }
@@ -178,7 +178,7 @@ public class Mp3ParserTest extends TikaTest {
         assertContains(", disc 1", content);
 
         assertEquals("MPEG 3 Layer III Version 1", metadata.get("version"));
-        assertEquals("44100", metadata.get("samplerate"));
+        assertEquals("44100", metadata.get(XMPDM.AUDIO_SAMPLE_RATE));
         assertEquals("1", metadata.get("channels"));
         checkDuration(metadata, 2);
 
@@ -214,7 +214,7 @@ public class Mp3ParserTest extends TikaTest {
                 metadata.get(XMPDM.LOG_COMMENT));
 
         assertEquals("MPEG 3 Layer III Version 1", metadata.get("version"));
-        assertEquals("44100", metadata.get("samplerate"));
+        assertEquals("44100", metadata.get(XMPDM.AUDIO_SAMPLE_RATE));
         assertEquals("1", metadata.get("channels"));
         checkDuration(metadata, 2);
     }
@@ -239,7 +239,7 @@ public class Mp3ParserTest extends TikaTest {
                 metadata.get(XMPDM.LOG_COMMENT));
 
         assertEquals("MPEG 3 Layer III Version 1", metadata.get("version"));
-        assertEquals("44100", metadata.get("samplerate"));
+        assertEquals("44100", metadata.get(XMPDM.AUDIO_SAMPLE_RATE));
         assertEquals("1", metadata.get("channels"));
         checkDuration(metadata, 2);
     }
@@ -269,7 +269,7 @@ public class Mp3ParserTest extends TikaTest {
         assertContains("Rock", content);
 
         assertEquals("MPEG 3 Layer III Version 1", metadata.get("version"));
-        assertEquals("44100", metadata.get("samplerate"));
+        assertEquals("44100", metadata.get(XMPDM.AUDIO_SAMPLE_RATE));
         assertEquals("2", metadata.get("channels"));
         checkDuration(metadata, 1);
     }
@@ -321,7 +321,7 @@ public class Mp3ParserTest extends TikaTest {
         assertContains("Plus loin vers l'ouest", content);
 
         assertEquals("MPEG 3 Layer III Version 1", metadata.get("version"));
-        assertEquals("44100", metadata.get("samplerate"));
+        assertEquals("44100", metadata.get(XMPDM.AUDIO_SAMPLE_RATE));
         assertEquals("2", metadata.get("channels"));
     }
 
@@ -349,7 +349,7 @@ public class Mp3ParserTest extends TikaTest {
 
         // File lacks any audio frames, so we can't know these
         assertEquals(null, metadata.get("version"));
-        assertEquals(null, metadata.get("samplerate"));
+        assertEquals(null, metadata.get(XMPDM.AUDIO_SAMPLE_RATE));
         assertEquals(null, metadata.get("channels"));
     }
 
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/video/FLVParserTest.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/video/FLVParserTest.java
index 2259f8a3d0..bd37dc3671 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/video/FLVParserTest.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-audiovideo-module/src/test/java/org/apache/tika/parser/video/FLVParserTest.java
@@ -35,11 +35,11 @@ public class FLVParserTest {
 
         assertEquals("", content);
         assertEquals("video/x-flv", metadata.get(Metadata.CONTENT_TYPE));
-        assertEquals("true", metadata.get("hasVideo"));
-        assertEquals("false", metadata.get("stereo"));
-        assertEquals("true", metadata.get("hasAudio"));
-        assertEquals("120.0", metadata.get("height"));
-        assertEquals("16.0", metadata.get("audiosamplesize"));
+        assertEquals("true", metadata.get("flv:hasVideo"));
+        assertEquals("false", metadata.get("flv:stereo"));
+        assertEquals("true", metadata.get("flv:hasAudio"));
+        assertEquals("120.0", metadata.get("flv:height"));
+        assertEquals("16.0", metadata.get("flv:audiosamplesize"));
     }
 
 }
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-cad-module/src/main/java/org/apache/tika/parser/dwg/DWGReadParser.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-cad-module/src/main/java/org/apache/tika/parser/dwg/DWGReadParser.java
index 682005ccee..f774723486 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-cad-module/src/main/java/org/apache/tika/parser/dwg/DWGReadParser.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-cad-module/src/main/java/org/apache/tika/parser/dwg/DWGReadParser.java
@@ -294,7 +294,7 @@ public class DWGReadParser extends AbstractDWGParser {
                 if (nextToken.isStructStart()) {
                     jsonParser.skipChildren();
                 } else if (nextToken.isScalarValue()) {
-                    metadata.set(nextFieldName, jsonParser.getText());
+                    metadata.set("dwg:" + nextFieldName, jsonParser.getText());
                 }
             }
         }
@@ -347,7 +347,7 @@ public class DWGReadParser extends AbstractDWGParser {
                         } else if ("HYPERLINKBASE".equals(nextFieldName)) {
                             metadata.set(TikaCoreProperties.RELATION, textVal);
                         } else if (!Strings.CI.startsWith(nextFieldName, 
"unknown")) {
-                            metadata.set(nextFieldName, textVal);
+                            metadata.set("dwg:" + nextFieldName, textVal);
                         }
                     }
                 }
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-code-module/src/main/java/org/apache/tika/parser/mat/MatParser.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-code-module/src/main/java/org/apache/tika/parser/mat/MatParser.java
index d203b4e7b7..4eac3bf833 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-code-module/src/main/java/org/apache/tika/parser/mat/MatParser.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-code-module/src/main/java/org/apache/tika/parser/mat/MatParser.java
@@ -36,6 +36,7 @@ import org.apache.tika.annotation.TikaComponent;
 import org.apache.tika.exception.TikaException;
 import org.apache.tika.io.TikaInputStream;
 import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.TikaCoreProperties;
 import org.apache.tika.mime.MediaType;
 import org.apache.tika.parser.ParseContext;
 import org.apache.tika.parser.Parser;
@@ -79,7 +80,7 @@ public class MatParser implements Parser {
         if (parts[2].contains("Created")) {
             int lastIndex1 = parts[2].lastIndexOf("Created on:");
             String dateCreated = parts[2].substring(lastIndex1 + "Created 
on:".length()).trim();
-            metadata.set("createdOn", dateCreated);
+            metadata.set(TikaCoreProperties.CREATED, dateCreated);
         }
 
         if (parts[1].contains("Platform")) {
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-code-module/src/test/java/org/apache/tika/parser/mat/MatParserTest.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-code-module/src/test/java/org/apache/tika/parser/mat/MatParserTest.java
index a93e347988..1d640fff2b 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-code-module/src/test/java/org/apache/tika/parser/mat/MatParserTest.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-code-module/src/test/java/org/apache/tika/parser/mat/MatParserTest.java
@@ -23,6 +23,7 @@ import org.junit.jupiter.api.Test;
 import org.apache.tika.TikaTest;
 import org.apache.tika.io.TikaInputStream;
 import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.TikaCoreProperties;
 import org.apache.tika.parser.ParseContext;
 import org.apache.tika.parser.Parser;
 import org.apache.tika.sax.ToXMLContentHandler;
@@ -42,7 +43,7 @@ public class MatParserTest extends TikaTest {
         assertEquals("PCWIN64", metadata.get("platform"));
         assertEquals("MATLAB 5.0 MAT-file", metadata.get("fileType"));
         assertEquals("IM", metadata.get("endian"));
-        assertEquals("Thu Feb 21 15:52:49 2013", metadata.get("createdOn"));
+        assertEquals("Thu Feb 21 15:52:49 2013", 
metadata.get(TikaCoreProperties.CREATED));
 
         assertContains("<li>[1x909  double array]</li>", content);
         assertContains("<p>c1:[1x1  struct array]</p>", content);
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-font-module/src/main/java/org/apache/tika/parser/font/TrueTypeParser.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-font-module/src/main/java/org/apache/tika/parser/font/TrueTypeParser.java
index 6e4b67f92f..1be6a091cd 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-font-module/src/main/java/org/apache/tika/parser/font/TrueTypeParser.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-font-module/src/main/java/org/apache/tika/parser/font/TrueTypeParser.java
@@ -103,7 +103,7 @@ public class TrueTypeParser implements Parser {
                     metadata.set(AdobeFontMetricParser.MET_PS_NAME, 
nr.getString());
                 }
                 if (nr.getNameId() == NameRecord.NAME_COPYRIGHT) {
-                    metadata.set("Copyright", nr.getString());
+                    metadata.set(TikaCoreProperties.RIGHTS, nr.getString());
                 }
                 if (nr.getNameId() == NameRecord.NAME_TRADEMARK) {
                     metadata.set("Trademark", nr.getString());
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-font-module/src/test/java/org/apache/tika/parser/font/FontParsersTest.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-font-module/src/test/java/org/apache/tika/parser/font/FontParsersTest.java
index ef99442b11..9cdb45be26 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-font-module/src/test/java/org/apache/tika/parser/font/FontParsersTest.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-font-module/src/test/java/org/apache/tika/parser/font/FontParsersTest.java
@@ -95,7 +95,7 @@ public class FontParsersTest extends TikaTest {
         assertEquals("Bold", metadata.get(MET_FONT_SUB_FAMILY_NAME));
         assertEquals("OpenSans-Bold", metadata.get(MET_PS_NAME));
 
-        assertEquals("Digitized", metadata.get("Copyright").substring(0, 9));
+        assertEquals("Digitized", 
metadata.get(TikaCoreProperties.RIGHTS).substring(0, 9));
         assertEquals("Open Sans", metadata.get("Trademark").substring(0, 9));
 
         // Not extracted
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/rtf/jflex/RTFEmbeddedHandlerTest.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/rtf/jflex/RTFEmbeddedHandlerTest.java
index 32b8ae58f9..c119ada099 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/rtf/jflex/RTFEmbeddedHandlerTest.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-microsoft-module/src/test/java/org/apache/tika/parser/microsoft/rtf/jflex/RTFEmbeddedHandlerTest.java
@@ -59,7 +59,7 @@ public class RTFEmbeddedHandlerTest {
                 Metadata copy = new Metadata();
                 for (String name : metadata.names()) {
                     for (String val : metadata.getValues(name)) {
-                        copy.add(name, val);
+                        copy.reconstruct(name, val, true);
                     }
                 }
                 extracted.add(copy);
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/main/java/org/apache/tika/parser/indesign/IDMLParser.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/main/java/org/apache/tika/parser/indesign/IDMLParser.java
index 1b8f7a5577..10261e9054 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/main/java/org/apache/tika/parser/indesign/IDMLParser.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/main/java/org/apache/tika/parser/indesign/IDMLParser.java
@@ -35,6 +35,7 @@ import org.apache.tika.annotation.TikaComponent;
 import org.apache.tika.exception.TikaException;
 import org.apache.tika.io.TikaInputStream;
 import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.Office;
 import org.apache.tika.mime.MediaType;
 import org.apache.tika.parser.ParseContext;
 import org.apache.tika.parser.Parser;
@@ -115,7 +116,7 @@ public class IDMLParser implements Parser {
 
         metadata.set("SpreadPageCount", Integer.toString(pageCount));
         metadata.set("MasterSpreadPageCount", 
Integer.toString(masterSpreadCount));
-        metadata.set("TotalPageCount", Integer.toString(pageCount + 
masterSpreadCount));
+        metadata.set(Office.PAGE_COUNT, Integer.toString(pageCount + 
masterSpreadCount));
 
         xhtml.endDocument();
 
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/main/java/org/apache/tika/parser/mif/MIFContentHandler.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/main/java/org/apache/tika/parser/mif/MIFContentHandler.java
index e76d15a9ee..a41ae1147c 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/main/java/org/apache/tika/parser/mif/MIFContentHandler.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/main/java/org/apache/tika/parser/mif/MIFContentHandler.java
@@ -23,6 +23,7 @@ import org.xml.sax.helpers.AttributesImpl;
 import org.xml.sax.helpers.DefaultHandler;
 
 import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.Office;
 import org.apache.tika.sax.XHTMLContentHandler;
 
 /**
@@ -113,7 +114,7 @@ public class MIFContentHandler extends DefaultHandler {
 
     @Override
     public void endDocument() {
-        metadata.set("PageCount", String.valueOf(bodyPageCount));
+        metadata.set(Office.PAGE_COUNT, String.valueOf(bodyPageCount));
         metadata.set("MasterPageCount", String.valueOf(masterPageCount));
         metadata.set("ReferencePageCount", String.valueOf(referencePageCount));
         metadata.set("TotalPageCount",
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/test/java/org/apache/tika/parser/indesign/IDMLParserTest.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/test/java/org/apache/tika/parser/indesign/IDMLParserTest.java
index 505a1e6f05..99293522c5 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/test/java/org/apache/tika/parser/indesign/IDMLParserTest.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/test/java/org/apache/tika/parser/indesign/IDMLParserTest.java
@@ -22,6 +22,7 @@ import org.junit.jupiter.api.Test;
 
 import org.apache.tika.TikaTest;
 import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.Office;
 import org.apache.tika.metadata.XMP;
 import org.apache.tika.parser.Parser;
 
@@ -40,7 +41,7 @@ public class IDMLParserTest extends TikaTest {
     public void testParserToText() throws Exception {
         Metadata metadata = new Metadata();
         String content = getText("testIndesign.idml", parser, metadata);
-        assertEquals("3", metadata.get("TotalPageCount"));
+        assertEquals("3", metadata.get(Office.PAGE_COUNT));
         assertEquals("2", metadata.get("MasterSpreadPageCount"));
         assertEquals("1", metadata.get("SpreadPageCount"));
         assertEquals("application/vnd.adobe.indesign-idml-package", 
metadata.get(Metadata.CONTENT_TYPE));
@@ -59,7 +60,7 @@ public class IDMLParserTest extends TikaTest {
         // failure below could be because of missing javax.xml.bind if xmpbox 
2.* is used
         // check the swallowed throwable in XMPMetadataExtractor.parse()
         assertEquals("Adobe InDesign CC 14.0 (Windows)", 
metadata.get(XMP.CREATOR_TOOL));
-        assertEquals("3", metadata.get("TotalPageCount"));
+        assertEquals("3", metadata.get(Office.PAGE_COUNT));
         assertContains("<p>Lorem ipsum dolor sit amet, consectetur adipiscing 
elit", xml);
         assertContains("<meta name=\"xmp:CreatorTool\" content=\"Adobe 
InDesign CC 14.0 (Windows)\" />", xml);
     }
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/test/java/org/apache/tika/parser/mif/MIFParserTest.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/test/java/org/apache/tika/parser/mif/MIFParserTest.java
index ed98728b02..066625bada 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/test/java/org/apache/tika/parser/mif/MIFParserTest.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/test/java/org/apache/tika/parser/mif/MIFParserTest.java
@@ -22,6 +22,7 @@ import org.junit.jupiter.api.Test;
 
 import org.apache.tika.TikaTest;
 import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.Office;
 import org.apache.tika.parser.Parser;
 
 /**
@@ -38,7 +39,7 @@ public class MIFParserTest extends TikaTest {
     public void testParserToText() throws Exception {
         Metadata metadata = new Metadata();
         String content = getText("testFramemakerMif.mif", parser, metadata);
-        assertEquals("1", metadata.get("PageCount"));
+        assertEquals("1", metadata.get(Office.PAGE_COUNT));
         assertEquals("application/x-mif", metadata.get(Metadata.CONTENT_TYPE));
         assertContains("Lorem ipsum dolor sit amet, consectetur adipiscing 
elit", content);
     }
@@ -47,7 +48,7 @@ public class MIFParserTest extends TikaTest {
     public void testParserToXML() throws Exception {
         Metadata metadata = new Metadata();
         String xml = getXML("testFramemakerMif.mif", parser, metadata).xml;
-        assertEquals("1", metadata.get("PageCount"));
+        assertEquals("1", metadata.get(Office.PAGE_COUNT));
         assertContains("<p>Lorem ipsum dolor sit amet, consectetur adipiscing 
elit", xml);
         assertContains("<meta name=\"Content-Type\" 
content=\"application/x-mif\" />", xml);
     }
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/main/java/org/apache/tika/parser/pdf/PDMetadataExtractor.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/main/java/org/apache/tika/parser/pdf/PDMetadataExtractor.java
index 8ffb16a628..ffe5474899 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/main/java/org/apache/tika/parser/pdf/PDMetadataExtractor.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-pdf-module/src/main/java/org/apache/tika/parser/pdf/PDMetadataExtractor.java
@@ -55,6 +55,7 @@ import org.apache.tika.parser.pdf.xmpschemas.XMPSchemaPDFVT;
 import org.apache.tika.parser.pdf.xmpschemas.XMPSchemaPDFX;
 import org.apache.tika.parser.pdf.xmpschemas.XMPSchemaPDFXId;
 import org.apache.tika.parser.xmp.JempboxExtractor;
+import org.apache.tika.utils.ExceptionUtils;
 import org.apache.tika.utils.StringUtils;
 import org.apache.tika.utils.XMLReaderUtils;
 
@@ -95,8 +96,8 @@ public class PDMetadataExtractor {
         try {
             schema = (XMPSchemaIllustrator) 
xmp.getSchemaByClass(XMPSchemaIllustrator.class);
         } catch (IOException e) {
-            metadata.set(TikaCoreProperties.TIKA_META_PREFIX + 
"pdf:metadata-xmp-parse-failed",
-                    "" + e);
+            metadata.add(TikaCoreProperties.TIKA_META_EXCEPTION_WARNING,
+                    ExceptionUtils.getStackTrace(e));
         }
 
         if (schema == null) {
@@ -145,8 +146,8 @@ public class PDMetadataExtractor {
         try {
             schema = (XMPSchemaPDFVT) 
xmp.getSchemaByClass(XMPSchemaPDFVT.class);
         } catch (IOException e) {
-            metadata.set(TikaCoreProperties.TIKA_META_PREFIX + 
"pdf:metadata-xmp-parse-failed",
-                    "" + e);
+            metadata.add(TikaCoreProperties.TIKA_META_EXCEPTION_WARNING,
+                    ExceptionUtils.getStackTrace(e));
         }
 
         if (schema == null) {
@@ -178,8 +179,8 @@ public class PDMetadataExtractor {
                 }
             }
         } catch (IOException e) {
-            metadata.set(TikaCoreProperties.TIKA_META_PREFIX + 
"pdf:metadata-xmp-parse-failed",
-                    "" + e);
+            metadata.add(TikaCoreProperties.TIKA_META_EXCEPTION_WARNING,
+                    ExceptionUtils.getStackTrace(e));
         }
         try {
             XMPSchemaPDFX XMPSchemaPDFX = (XMPSchemaPDFX) 
xmp.getSchemaByClass(XMPSchemaPDFX.class);
@@ -194,8 +195,8 @@ public class PDMetadataExtractor {
                 }
             }
         } catch (IOException e) {
-            metadata.set(TikaCoreProperties.TIKA_META_PREFIX + 
"pdf:metadata-xmp-parse-failed",
-                    "" + e);
+            metadata.add(TikaCoreProperties.TIKA_META_EXCEPTION_WARNING,
+                    ExceptionUtils.getStackTrace(e));
         }
 
     }
@@ -207,8 +208,8 @@ public class PDMetadataExtractor {
         try {
             schema = (XMPSchemaPDFUA) 
xmp.getSchemaByClass(XMPSchemaPDFUA.class);
         } catch (IOException e) {
-            metadata.set(TikaCoreProperties.TIKA_META_PREFIX + 
"pdf:metadata-xmp-parse-failed",
-                    "" + e);
+            metadata.add(TikaCoreProperties.TIKA_META_EXCEPTION_WARNING,
+                    ExceptionUtils.getStackTrace(e));
         }
 
         if (schema == null) {
@@ -232,8 +233,8 @@ public class PDMetadataExtractor {
         try {
             schema = (XMPSchemaPDFAId) 
xmp.getSchemaByClass(XMPSchemaPDFAId.class);
         } catch (IOException e) {
-            metadata.set(TikaCoreProperties.TIKA_META_PREFIX + 
"pdf:metadata-xmp-parse-failed",
-                    "" + e);
+            metadata.add(TikaCoreProperties.TIKA_META_EXCEPTION_WARNING,
+                    ExceptionUtils.getStackTrace(e));
         }
 
         if (schema == null) {
diff --git 
a/tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/serialization/FetchEmitTupleDeserializer.java
 
b/tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/serialization/FetchEmitTupleDeserializer.java
index f7ab29295b..338da15e9d 100644
--- 
a/tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/serialization/FetchEmitTupleDeserializer.java
+++ 
b/tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/serialization/FetchEmitTupleDeserializer.java
@@ -96,10 +96,10 @@ public class FetchEmitTupleDeserializer extends 
JsonDeserializer<FetchEmitTuple>
             String k = e.getKey();
             if (vals.isArray()) {
                 for (JsonNode arrVal : vals) {
-                    metadata.add(k, arrVal.textValue());
+                    metadata.reconstruct(k, arrVal.textValue(), true);
                 }
             } else {
-                metadata.set(k, vals.asText());
+                metadata.reconstruct(k, vals.asText(), false);
             }
         }
         return metadata;
diff --git 
a/tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/server/EmitHandler.java
 
b/tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/server/EmitHandler.java
index 94074e048a..4245b9e631 100644
--- 
a/tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/server/EmitHandler.java
+++ 
b/tika-pipes/tika-pipes-core/src/main/java/org/apache/tika/pipes/core/server/EmitHandler.java
@@ -231,12 +231,19 @@ class EmitHandler {
     }
 
     private void injectUserMetadata(Metadata userMetadata, List<Metadata> 
metadataList) {
-        for (String n : userMetadata.names()) {
-            //overwrite whatever was there
-            metadataList.get(0).set(n, null);
-            for (String val : userMetadata.getValues(n)) {
-                metadataList.get(0).add(n, val);
+        Metadata target = metadataList.get(0);
+        boolean prev = target.isTrusted();
+        target.setTrusted(true);
+        try {
+            for (String n : userMetadata.names()) {
+                //overwrite whatever was there
+                target.set(n, null);
+                for (String val : userMetadata.getValues(n)) {
+                    target.add(n, val);
+                }
             }
+        } finally {
+            target.setTrusted(prev);
         }
     }
 
diff --git 
a/tika-pipes/tika-pipes-integration-tests/src/test/java/org/apache/tika/metadata/filter/AttachmentCountingListFilter.java
 
b/tika-pipes/tika-pipes-integration-tests/src/test/java/org/apache/tika/metadata/filter/AttachmentCountingListFilter.java
index fe72ceef11..4706dbe9d0 100644
--- 
a/tika-pipes/tika-pipes-integration-tests/src/test/java/org/apache/tika/metadata/filter/AttachmentCountingListFilter.java
+++ 
b/tika-pipes/tika-pipes-integration-tests/src/test/java/org/apache/tika/metadata/filter/AttachmentCountingListFilter.java
@@ -29,7 +29,7 @@ public class AttachmentCountingListFilter extends 
MetadataFilter {
     private Integer count = 0;
 
     @Override
-    public void filter(List<Metadata> metadataList, ParseContext parseContext) 
throws TikaException {
+    protected void doFilter(List<Metadata> metadataList, ParseContext 
parseContext) throws TikaException {
         if (metadataList == null || metadataList.isEmpty()) {
             return;
         }
diff --git 
a/tika-serialization/src/main/java/org/apache/tika/serialization/serdes/MetadataDeserializer.java
 
b/tika-serialization/src/main/java/org/apache/tika/serialization/serdes/MetadataDeserializer.java
index f02e12f30e..248783f97f 100644
--- 
a/tika-serialization/src/main/java/org/apache/tika/serialization/serdes/MetadataDeserializer.java
+++ 
b/tika-serialization/src/main/java/org/apache/tika/serialization/serdes/MetadataDeserializer.java
@@ -50,14 +50,14 @@ public class MetadataDeserializer extends 
JsonDeserializer<Metadata> {
         }
 
         if (token.isScalarValue()) {
-            metadata.set(field, jsonParser.getText());
+            metadata.reconstruct(field, jsonParser.getText(), false);
         } else if (jsonParser.isExpectedStartArrayToken()) {
             token = jsonParser.nextToken();
             while (token != null) {
                 if (token == JsonToken.END_ARRAY) {
                     return true;
                 } else if (token.isScalarValue()) {
-                    metadata.add(field, jsonParser.getText());
+                    metadata.reconstruct(field, jsonParser.getText(), true);
                 } else {
                     break;
                 }
diff --git 
a/tika-serialization/src/test/java/org/apache/tika/metadata/filter/AttachmentCountingListFilter.java
 
b/tika-serialization/src/test/java/org/apache/tika/metadata/filter/AttachmentCountingListFilter.java
index 1d1e6083f5..5cde289d8e 100644
--- 
a/tika-serialization/src/test/java/org/apache/tika/metadata/filter/AttachmentCountingListFilter.java
+++ 
b/tika-serialization/src/test/java/org/apache/tika/metadata/filter/AttachmentCountingListFilter.java
@@ -28,7 +28,7 @@ public class AttachmentCountingListFilter extends 
MetadataFilter {
 
     private Integer count = 0;
     @Override
-    public void filter(List<Metadata> metadataList, ParseContext parseContext) 
throws TikaException {
+    protected void doFilter(List<Metadata> metadataList, ParseContext 
parseContext) throws TikaException {
         if (metadataList == null || metadataList.isEmpty()) {
             return;
         }
diff --git 
a/tika-serialization/src/test/java/org/apache/tika/serialization/CustomClassSerializationTest.java
 
b/tika-serialization/src/test/java/org/apache/tika/serialization/CustomClassSerializationTest.java
index 426493b1b3..e660cc2acc 100644
--- 
a/tika-serialization/src/test/java/org/apache/tika/serialization/CustomClassSerializationTest.java
+++ 
b/tika-serialization/src/test/java/org/apache/tika/serialization/CustomClassSerializationTest.java
@@ -64,7 +64,7 @@ public class CustomClassSerializationTest {
         }
 
         @Override
-        public void filter(java.util.List<Metadata> metadataList, ParseContext 
parseContext) {
+        protected void doFilter(java.util.List<Metadata> metadataList, 
ParseContext parseContext) {
             for (Metadata metadata : metadataList) {
                 for (String name : metadata.names()) {
                     String[] values = metadata.getValues(name);
diff --git 
a/tika-serialization/src/test/java/org/apache/tika/serialization/JsonMetadataTest.java
 
b/tika-serialization/src/test/java/org/apache/tika/serialization/JsonMetadataTest.java
index e62289ba5d..d2fa94b6b0 100644
--- 
a/tika-serialization/src/test/java/org/apache/tika/serialization/JsonMetadataTest.java
+++ 
b/tika-serialization/src/test/java/org/apache/tika/serialization/JsonMetadataTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.tika.serialization;
 
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -130,4 +131,25 @@ public class JsonMetadataTest {
         Metadata deserialized = JsonMetadata.fromJson(new 
StringReader(writer.toString()));
         assertEquals(m, deserialized);
     }
+
+    @Test
+    public void testInternalKeysRoundTrip() throws Exception {
+        //reserved keys must survive serialize->deserialize (reconstructed via 
Property).
+        Metadata m = new Metadata();
+        m.set(TikaCoreProperties.TIKA_CONTENT, "the content");
+        m.add(TikaCoreProperties.TIKA_PARSED_BY, 
"org.apache.tika.parser.DefaultParser");
+        m.add(TikaCoreProperties.TIKA_PARSED_BY, 
"org.apache.tika.parser.pdf.PDFParser");
+        m.add("k1", "v1");
+
+        StringWriter writer = new StringWriter();
+        JsonMetadata.toJson(m, writer);
+        Metadata deserialized = JsonMetadata.fromJson(new 
StringReader(writer.toString()));
+
+        assertEquals("the content", 
deserialized.get(TikaCoreProperties.TIKA_CONTENT));
+        assertArrayEquals(
+                new String[] {"org.apache.tika.parser.DefaultParser",
+                        "org.apache.tika.parser.pdf.PDFParser"},
+                deserialized.getValues(TikaCoreProperties.TIKA_PARSED_BY));
+        assertEquals(m, deserialized);
+    }
 }


Reply via email to