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

ndipiazza pushed a commit to branch TIKA-4606-ignite-3x-upgrade-fresh
in repository https://gitbox.apache.org/repos/asf/tika.git

commit 7af2fa2375b6d385dac68fe12da0d9f8158df322
Author: Nicholas DiPiazza <[email protected]>
AuthorDate: Fri Feb 27 13:53:54 2026 -0600

    TIKA-4606: Upgrade Apache Ignite from 2.x to 3.x
    
    - Replace Ignite 2.x embedded server with Ignite 3.x IgniteServer API
    - Replace Ignite 2.x cache API with Ignite 3.x KeyValueView/Table API
    - Remove H2 dependency (Ignite 3.x uses built-in Calcite SQL engine)
    - Remove ignite-spring, ignite-indexing, ignite-core 2.x dependencies
    - Add ignite-api, ignite-client, ignite-runner, ignite-sql-engine, 
ignite-table 3.x dependencies
    - Use text block for HOCON configuration in IgniteStoreServer
    - Add IgniteConfigStoreConfig with tableName/replicas/partitions (replacing 
cacheName/cacheMode)
    - Add IgniteStoreServer lifecycle to TikaGrpcServerImpl (start/shutdown)
    - Add TikaGrpcServerImpl.shutdown() and wire into TikaGrpcServer.stop()
    - Add emitter_id field to FetchAndParseRequest proto
    - Add NO_EMIT null check in EmitHandler to skip emission when no emitter 
specified
    - Add Ignite 3.x convergence dependencies to tika-parent (asm, picocli, 
snakeyaml, javax.validation)
    - Update tika-grpc shade plugin to use Ignite 3.x artifacts
    - Update all Ignite config JSON files from 2.x format to 3.x format
    
    Co-authored-by: Copilot <[email protected]>
---
 tika-grpc/dev-tika-config.json                     |   2 +-
 tika-grpc/pom.xml                                  |  14 +-
 .../org/apache/tika/pipes/grpc/TikaGrpcServer.java |   7 +-
 .../apache/tika/pipes/grpc/TikaGrpcServerImpl.java |  39 +++--
 tika-grpc/src/main/proto/tika.proto                |   2 +
 .../src/test/resources/tika-config-ignite.json     |   2 +-
 tika-parent/pom.xml                                |  21 +++
 tika-pipes/tika-pipes-config-store-ignite/pom.xml  | 106 +++++++++---
 .../tika/pipes/ignite/ExtensionConfigDTO.java      |  29 +---
 .../tika/pipes/ignite/IgniteConfigStore.java       | 182 +++++++++++---------
 .../ignite/config/IgniteConfigStoreConfig.java     |  59 +++----
 .../pipes/ignite/server/IgniteStoreServer.java     | 184 ++++++++++++---------
 .../tika/pipes/ignite/IgniteConfigStoreTest.java   | 119 ++++++-------
 .../apache/tika/pipes/core/server/EmitHandler.java |   5 +
 14 files changed, 435 insertions(+), 336 deletions(-)

diff --git a/tika-grpc/dev-tika-config.json b/tika-grpc/dev-tika-config.json
index 6261687e14..4636fab46b 100644
--- a/tika-grpc/dev-tika-config.json
+++ b/tika-grpc/dev-tika-config.json
@@ -16,7 +16,7 @@
   ],
   "pipes": {
     "configStoreType": "ignite",
-    "configStoreParams": 
"{\"cacheName\":\"tika-config-cache\",\"cacheMode\":\"REPLICATED\",\"igniteInstanceName\":\"TikaGrpcIgnite\",\"autoClose\":true}"
+    "configStoreParams": 
"{\"tableName\":\"tika-config-cache\",\"igniteInstanceName\":\"TikaGrpcIgnite\",\"replicas\":2,\"partitions\":10,\"autoClose\":true}"
   },
   "fetchers": [
     {
diff --git a/tika-grpc/pom.xml b/tika-grpc/pom.xml
index 89945799e1..456f2ff977 100644
--- a/tika-grpc/pom.xml
+++ b/tika-grpc/pom.xml
@@ -596,18 +596,18 @@
         </dependency>
         <dependency>
           <groupId>org.apache.ignite</groupId>
-          <artifactId>ignite-core</artifactId>
-          <version>2.16.0</version>
+          <artifactId>ignite-api</artifactId>
+          <version>3.1.0</version>
         </dependency>
         <dependency>
           <groupId>org.apache.ignite</groupId>
-          <artifactId>ignite-indexing</artifactId>
-          <version>2.16.0</version>
+          <artifactId>ignite-client</artifactId>
+          <version>3.1.0</version>
         </dependency>
         <dependency>
-          <groupId>com.h2database</groupId>
-          <artifactId>h2</artifactId>
-          <version>1.4.197</version>
+          <groupId>org.apache.ignite</groupId>
+          <artifactId>ignite-runner</artifactId>
+          <version>3.1.0</version>
         </dependency>
         <dependency>
           <groupId>org.apache.tika</groupId>
diff --git 
a/tika-grpc/src/main/java/org/apache/tika/pipes/grpc/TikaGrpcServer.java 
b/tika-grpc/src/main/java/org/apache/tika/pipes/grpc/TikaGrpcServer.java
index 810f58961c..a576ba22c2 100644
--- a/tika-grpc/src/main/java/org/apache/tika/pipes/grpc/TikaGrpcServer.java
+++ b/tika-grpc/src/main/java/org/apache/tika/pipes/grpc/TikaGrpcServer.java
@@ -40,6 +40,7 @@ public class TikaGrpcServer {
     private static final Logger LOGGER = 
LoggerFactory.getLogger(TikaGrpcServer.class);
     public static final int TIKA_SERVER_GRPC_DEFAULT_PORT = 50052;
     private Server server;
+    private TikaGrpcServerImpl serviceImpl;
     @Parameter(names = {"-p", "--port"}, description = "The grpc server port", 
help = true)
     private Integer port = TIKA_SERVER_GRPC_DEFAULT_PORT;
 
@@ -94,9 +95,10 @@ public class TikaGrpcServer {
         }
         File tikaConfigFile = new File(tikaConfig.getAbsolutePath());
         healthStatusManager.setStatus(TikaGrpcServer.class.getSimpleName(), 
ServingStatus.SERVING);
+        serviceImpl = new TikaGrpcServerImpl(tikaConfigFile.getAbsolutePath(), 
pluginRoots);
         server = Grpc
                 .newServerBuilderForPort(port, creds)
-                .addService(new 
TikaGrpcServerImpl(tikaConfigFile.getAbsolutePath(), pluginRoots))
+                .addService(serviceImpl)
                 .addService(healthStatusManager.getHealthService())
                 .addService(ProtoReflectionServiceV1.newInstance())
                 .build()
@@ -118,6 +120,9 @@ public class TikaGrpcServer {
     }
 
     public void stop() throws InterruptedException {
+        if (serviceImpl != null) {
+            serviceImpl.shutdown();
+        }
         if (server != null) {
             server
                     .shutdown()
diff --git 
a/tika-grpc/src/main/java/org/apache/tika/pipes/grpc/TikaGrpcServerImpl.java 
b/tika-grpc/src/main/java/org/apache/tika/pipes/grpc/TikaGrpcServerImpl.java
index 2885ff0020..c61085560b 100644
--- a/tika-grpc/src/main/java/org/apache/tika/pipes/grpc/TikaGrpcServerImpl.java
+++ b/tika-grpc/src/main/java/org/apache/tika/pipes/grpc/TikaGrpcServerImpl.java
@@ -71,6 +71,7 @@ import org.apache.tika.pipes.core.PipesConfig;
 import org.apache.tika.pipes.core.config.ConfigStore;
 import org.apache.tika.pipes.core.config.ConfigStoreFactory;
 import org.apache.tika.pipes.core.fetcher.FetcherManager;
+import org.apache.tika.pipes.ignite.server.IgniteStoreServer;
 import org.apache.tika.plugins.ExtensionConfig;
 import org.apache.tika.plugins.TikaPluginManager;
 
@@ -89,6 +90,7 @@ class TikaGrpcServerImpl extends TikaGrpc.TikaImplBase {
     ConfigStore configStore;
     Path tikaConfigPath;
     PluginManager pluginManager;
+    private IgniteStoreServer igniteStoreServer;
 
     TikaGrpcServerImpl(String tikaConfigPath) throws TikaConfigException, 
IOException {
         this(tikaConfigPath, null);
@@ -153,24 +155,18 @@ class TikaGrpcServerImpl extends TikaGrpc.TikaImplBase {
     private void startIgniteServer(ExtensionConfig config) {
         try {
             LOG.info("Starting embedded Ignite server for ConfigStore");
-            
-            // Parse config to get Ignite settings
+
             com.fasterxml.jackson.databind.ObjectMapper mapper = new 
com.fasterxml.jackson.databind.ObjectMapper();
             com.fasterxml.jackson.databind.JsonNode params = 
mapper.readTree(config.json());
-            
-            String cacheName = params.has("cacheName") ? 
params.get("cacheName").asText() : "tika-config-store";
-            String cacheMode = params.has("cacheMode") ? 
params.get("cacheMode").asText() : "REPLICATED";
+
+            String tableName = params.has("tableName") ? 
params.get("tableName").asText() :
+                               params.has("cacheName") ? 
params.get("cacheName").asText() : "tika_config_store";
             String instanceName = params.has("igniteInstanceName") ? 
params.get("igniteInstanceName").asText() : "TikaIgniteServer";
-            
-            // Direct instantiation - no reflection needed
-            org.apache.ignite.cache.CacheMode mode = 
org.apache.ignite.cache.CacheMode.valueOf(cacheMode);
-            org.apache.tika.pipes.ignite.server.IgniteStoreServer server = 
-                new 
org.apache.tika.pipes.ignite.server.IgniteStoreServer(cacheName, mode, 
instanceName);
-            
-            server.startAsync();
-            
+
+            igniteStoreServer = new IgniteStoreServer(tableName, instanceName);
+            igniteStoreServer.start();
+
             LOG.info("Embedded Ignite server started successfully");
-            
         } catch (Exception e) {
             LOG.error("Failed to start embedded Ignite server", e);
             throw new RuntimeException("Failed to start Ignite server", e);
@@ -478,4 +474,19 @@ class TikaGrpcServerImpl extends TikaGrpc.TikaImplBase {
                     .asRuntimeException());
         }
     }
+
+    /**
+     * Releases resources, including the embedded Ignite server if one was 
started.
+     */
+    public void shutdown() {
+        if (igniteStoreServer != null) {
+            LOG.info("Shutting down embedded Ignite server");
+            try {
+                igniteStoreServer.close();
+                igniteStoreServer = null;
+            } catch (Exception e) {
+                LOG.error("Error shutting down Ignite server", e);
+            }
+        }
+    }
 }
diff --git a/tika-grpc/src/main/proto/tika.proto 
b/tika-grpc/src/main/proto/tika.proto
index aeb614decd..70fdf2f0af 100644
--- a/tika-grpc/src/main/proto/tika.proto
+++ b/tika-grpc/src/main/proto/tika.proto
@@ -98,6 +98,8 @@ message FetchAndParseRequest {
   // You can supply additional fetch configuration using this. Follows same 
fetch configuration json schema
   // as the fetcher configuration.
   string additional_fetch_config_json = 3;
+  // The ID of the emitter to use (optional). If not provided, no emitter will 
be used.
+  string emitter_id = 4;
 }
 
 message FetchAndParseReply {
diff --git a/tika-grpc/src/test/resources/tika-config-ignite.json 
b/tika-grpc/src/test/resources/tika-config-ignite.json
index 7f813c728a..12908fef18 100644
--- a/tika-grpc/src/test/resources/tika-config-ignite.json
+++ b/tika-grpc/src/test/resources/tika-config-ignite.json
@@ -1,7 +1,7 @@
 {
   "pipes": {
     "configStoreType": "ignite",
-    "configStoreParams": 
"{\"cacheName\":\"my-tika-cache\",\"cacheMode\":\"REPLICATED\",\"igniteInstanceName\":\"MyTikaCluster\",\"autoClose\":true}"
+    "configStoreParams": 
"{\"tableName\":\"my-tika-cache\",\"igniteInstanceName\":\"MyTikaCluster\",\"replicas\":2,\"partitions\":10,\"autoClose\":true}"
   },
   "fetchers": [
     {
diff --git a/tika-parent/pom.xml b/tika-parent/pom.xml
index 1682b3326c..8fa540803d 100644
--- a/tika-parent/pom.xml
+++ b/tika-parent/pom.xml
@@ -1144,6 +1144,27 @@
         <artifactId>nimbus-jose-jwt</artifactId>
         <version>${nimbus-jose-jwt.version}</version>
       </dependency>
+      <!-- Ignite 3.x convergence fixes -->
+      <dependency>
+        <groupId>org.ow2.asm</groupId>
+        <artifactId>asm</artifactId>
+        <version>${asm.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>info.picocli</groupId>
+        <artifactId>picocli</artifactId>
+        <version>4.7.7</version>
+      </dependency>
+      <dependency>
+        <groupId>org.yaml</groupId>
+        <artifactId>snakeyaml</artifactId>
+        <version>2.4</version>
+      </dependency>
+      <dependency>
+        <groupId>javax.validation</groupId>
+        <artifactId>validation-api</artifactId>
+        <version>2.0.1.Final</version>
+      </dependency>
     </dependencies>
   </dependencyManagement>
 
diff --git a/tika-pipes/tika-pipes-config-store-ignite/pom.xml 
b/tika-pipes/tika-pipes-config-store-ignite/pom.xml
index a45207bf11..af499351d6 100644
--- a/tika-pipes/tika-pipes-config-store-ignite/pom.xml
+++ b/tika-pipes/tika-pipes-config-store-ignite/pom.xml
@@ -30,11 +30,79 @@
   <packaging>jar</packaging>
 
   <properties>
-    <ignite.version>2.17.0</ignite.version>
-    <!-- Ignite 2.16.0 requires H2 1.4.x - not compatible with 2.x -->
-    <h2.version>1.4.197</h2.version>
+    <ignite.version>3.1.0</ignite.version>
   </properties>
 
+  <dependencyManagement>
+    <dependencies>
+      <dependency>
+        <groupId>org.jetbrains.kotlin</groupId>
+        <artifactId>kotlin-stdlib</artifactId>
+        <version>2.2.0</version>
+      </dependency>
+      <dependency>
+        <groupId>info.picocli</groupId>
+        <artifactId>picocli</artifactId>
+        <version>4.7.5</version>
+      </dependency>
+      <dependency>
+        <groupId>io.micronaut</groupId>
+        <artifactId>micronaut-inject</artifactId>
+        <version>3.10.4</version>
+      </dependency>
+      <dependency>
+        <groupId>io.micronaut</groupId>
+        <artifactId>micronaut-runtime</artifactId>
+        <version>3.10.4</version>
+      </dependency>
+      <dependency>
+        <groupId>io.micronaut</groupId>
+        <artifactId>micronaut-http-server</artifactId>
+        <version>3.10.4</version>
+      </dependency>
+      <dependency>
+        <groupId>io.micronaut</groupId>
+        <artifactId>micronaut-websocket</artifactId>
+        <version>3.10.4</version>
+      </dependency>
+      <dependency>
+        <groupId>io.micronaut</groupId>
+        <artifactId>micronaut-http-netty</artifactId>
+        <version>3.10.4</version>
+      </dependency>
+      <dependency>
+        <groupId>io.micronaut</groupId>
+        <artifactId>micronaut-http</artifactId>
+        <version>3.10.4</version>
+      </dependency>
+      <dependency>
+        <groupId>io.micronaut</groupId>
+        <artifactId>micronaut-validation</artifactId>
+        <version>3.10.4</version>
+      </dependency>
+      <dependency>
+        <groupId>io.micronaut</groupId>
+        <artifactId>micronaut-http-client-core</artifactId>
+        <version>3.10.4</version>
+      </dependency>
+      <dependency>
+        <groupId>jakarta.inject</groupId>
+        <artifactId>jakarta.inject-api</artifactId>
+        <version>2.0.1</version>
+      </dependency>
+      <dependency>
+        <groupId>io.swagger.core.v3</groupId>
+        <artifactId>swagger-annotations</artifactId>
+        <version>2.2.38</version>
+      </dependency>
+      <dependency>
+        <groupId>org.yaml</groupId>
+        <artifactId>snakeyaml</artifactId>
+        <version>2.4</version>
+      </dependency>
+    </dependencies>
+  </dependencyManagement>
+
   <dependencies>
     <dependency>
       <groupId>${project.groupId}</groupId>
@@ -57,37 +125,28 @@
     </dependency>
     <dependency>
       <groupId>org.apache.ignite</groupId>
-      <artifactId>ignite-core</artifactId>
+      <artifactId>ignite-api</artifactId>
+      <version>${ignite.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.ignite</groupId>
+      <artifactId>ignite-client</artifactId>
       <version>${ignite.version}</version>
     </dependency>
     <dependency>
       <groupId>org.apache.ignite</groupId>
-      <artifactId>ignite-indexing</artifactId>
+      <artifactId>ignite-runner</artifactId>
       <version>${ignite.version}</version>
     </dependency>
     <dependency>
       <groupId>org.apache.ignite</groupId>
-      <artifactId>ignite-spring</artifactId>
+      <artifactId>ignite-sql-engine</artifactId>
       <version>${ignite.version}</version>
-      <exclusions>
-        <exclusion>
-          <groupId>org.springframework</groupId>
-          <artifactId>spring-core</artifactId>
-        </exclusion>
-        <exclusion>
-          <groupId>org.springframework</groupId>
-          <artifactId>spring-beans</artifactId>
-        </exclusion>
-        <exclusion>
-          <groupId>org.springframework</groupId>
-          <artifactId>spring-context</artifactId>
-        </exclusion>
-      </exclusions>
     </dependency>
     <dependency>
-      <groupId>com.h2database</groupId>
-      <artifactId>h2</artifactId>
-      <version>${h2.version}</version>
+      <groupId>org.apache.ignite</groupId>
+      <artifactId>ignite-table</artifactId>
+      <version>${ignite.version}</version>
     </dependency>
     <dependency>
       <groupId>${project.groupId}</groupId>
@@ -120,7 +179,6 @@
           </archive>
         </configuration>
       </plugin>
-
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-surefire-plugin</artifactId>
diff --git 
a/tika-pipes/tika-pipes-config-store-ignite/src/main/java/org/apache/tika/pipes/ignite/ExtensionConfigDTO.java
 
b/tika-pipes/tika-pipes-config-store-ignite/src/main/java/org/apache/tika/pipes/ignite/ExtensionConfigDTO.java
index 26e8cdb054..089d8b826f 100644
--- 
a/tika-pipes/tika-pipes-config-store-ignite/src/main/java/org/apache/tika/pipes/ignite/ExtensionConfigDTO.java
+++ 
b/tika-pipes/tika-pipes-config-store-ignite/src/main/java/org/apache/tika/pipes/ignite/ExtensionConfigDTO.java
@@ -18,47 +18,24 @@ package org.apache.tika.pipes.ignite;
 
 import java.io.Serializable;
 
-import org.apache.tika.plugins.ExtensionConfig;
-
 /**
- * Serializable wrapper for ExtensionConfig to work with Ignite's binary 
serialization.
- * Since ExtensionConfig is a Java record with final fields, it cannot be 
directly
- * serialized by Ignite. This DTO provides mutable fields that Ignite can work 
with.
+ * Value object for storing configuration in an Ignite 3.x {@code 
KeyValueView}.
+ * The key ({@code id}) is managed separately as the map key.
  */
 public class ExtensionConfigDTO implements Serializable {
     private static final long serialVersionUID = 1L;
 
-    private String id;
     private String name;
     private String json;
 
     public ExtensionConfigDTO() {
     }
 
-    public ExtensionConfigDTO(String id, String name, String json) {
-        this.id = id;
+    public ExtensionConfigDTO(String name, String json) {
         this.name = name;
         this.json = json;
     }
 
-    public ExtensionConfigDTO(ExtensionConfig config) {
-        this.id = config.id();
-        this.name = config.name();
-        this.json = config.json();
-    }
-
-    public ExtensionConfig toExtensionConfig() {
-        return new ExtensionConfig(id, name, json);
-    }
-
-    public String getId() {
-        return id;
-    }
-
-    public void setId(String id) {
-        this.id = id;
-    }
-
     public String getName() {
         return name;
     }
diff --git 
a/tika-pipes/tika-pipes-config-store-ignite/src/main/java/org/apache/tika/pipes/ignite/IgniteConfigStore.java
 
b/tika-pipes/tika-pipes-config-store-ignite/src/main/java/org/apache/tika/pipes/ignite/IgniteConfigStore.java
index 05becb62bf..e57bd62850 100644
--- 
a/tika-pipes/tika-pipes-config-store-ignite/src/main/java/org/apache/tika/pipes/ignite/IgniteConfigStore.java
+++ 
b/tika-pipes/tika-pipes-config-store-ignite/src/main/java/org/apache/tika/pipes/ignite/IgniteConfigStore.java
@@ -16,14 +16,12 @@
  */
 package org.apache.tika.pipes.ignite;
 
+import java.util.HashSet;
 import java.util.Set;
 
 import org.apache.ignite.Ignite;
-import org.apache.ignite.IgniteCache;
-import org.apache.ignite.Ignition;
-import org.apache.ignite.cache.CacheMode;
-import org.apache.ignite.configuration.CacheConfiguration;
-import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.table.KeyValueView;
+import org.apache.ignite.table.Table;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -33,50 +31,46 @@ import 
org.apache.tika.pipes.ignite.config.IgniteConfigStoreConfig;
 import org.apache.tika.plugins.ExtensionConfig;
 
 /**
- * Apache Ignite-based implementation of {@link ConfigStore}.
- * Provides distributed configuration storage for Tika Pipes clustering.
+ * Apache Ignite 3.x-based implementation of {@link ConfigStore}.
+ * Provides distributed configuration storage for Tika Pipes using the Ignite 
3.x client API.
  * <p>
  * This implementation is thread-safe and suitable for multi-instance 
deployments
  * where configurations need to be shared across multiple servers.
  * <p>
- * Configuration options:
- * <ul>
- *   <li>cacheName - Name of the Ignite cache (default: 
"tika-config-store")</li>
- *   <li>cacheMode - Cache replication mode: PARTITIONED or REPLICATED 
(default: REPLICATED)</li>
- *   <li>igniteInstanceName - Name of the Ignite instance (default: 
"TikaIgniteConfigStore")</li>
- * </ul>
+ * Note: This uses Ignite 3.x with built-in Apache Calcite SQL engine (no H2 
dependency).
  */
 public class IgniteConfigStore implements ConfigStore {
 
     private static final Logger LOG = 
LoggerFactory.getLogger(IgniteConfigStore.class);
-    private static final String DEFAULT_CACHE_NAME = "tika-config-store";
+    private static final String DEFAULT_TABLE_NAME = "tika_config_store";
     private static final String DEFAULT_INSTANCE_NAME = 
"TikaIgniteConfigStore";
 
     private Ignite ignite;
-    private IgniteCache<String, ExtensionConfigDTO> cache;
-    private String cacheName = DEFAULT_CACHE_NAME;
-    private CacheMode cacheMode = CacheMode.REPLICATED;
+    private KeyValueView<String, ExtensionConfigDTO> kvView;
+    private String tableName = DEFAULT_TABLE_NAME;
+    private int replicas = 2;
+    private int partitions = 10;
     private String igniteInstanceName = DEFAULT_INSTANCE_NAME;
     private boolean autoClose = true;
     private ExtensionConfig extensionConfig;
     private boolean closed = false;
-    private boolean clientMode = true;  // Default to client mode
 
     public IgniteConfigStore() {
     }
 
     public IgniteConfigStore(ExtensionConfig extensionConfig) throws 
TikaConfigException {
         this.extensionConfig = extensionConfig;
-        
+
         IgniteConfigStoreConfig config = 
IgniteConfigStoreConfig.load(extensionConfig.json());
-        this.cacheName = config.getCacheName();
-        this.cacheMode = config.getCacheModeEnum();
+        this.tableName = config.getTableName();
+        this.replicas = config.getReplicas();
+        this.partitions = config.getPartitions();
         this.igniteInstanceName = config.getIgniteInstanceName();
         this.autoClose = config.isAutoClose();
     }
 
-    public IgniteConfigStore(String cacheName) {
-        this.cacheName = cacheName;
+    public IgniteConfigStore(String tableName) {
+        this.tableName = tableName;
     }
 
     @Override
@@ -94,105 +88,129 @@ public class IgniteConfigStore implements ConfigStore {
             return;
         }
 
-        LOG.info("Initializing IgniteConfigStore with cache: {}, mode: {}, 
instance: {}",
-                cacheName, cacheMode, igniteInstanceName);
-
-        // Disable Ignite's Object Input Filter autoconfiguration to avoid 
conflicts
-        
System.setProperty("IGNITE_ENABLE_OBJECT_INPUT_FILTER_AUTOCONFIGURATION", 
"false");
-
-        IgniteConfiguration cfg = new IgniteConfiguration();
-        cfg.setIgniteInstanceName(igniteInstanceName + (clientMode ? "-Client" 
: ""));
-        cfg.setClientMode(clientMode);
-        cfg.setPeerClassLoadingEnabled(false);  // Disable to avoid 
classloader conflicts
-        
-        // Set work directory to /var/cache/tika to match Tika's cache location
-        cfg.setWorkDirectory(System.getProperty("ignite.work.dir", 
"/var/cache/tika/ignite-work"));
-
-        ignite = Ignition.start(cfg);
-
-        // Get cache (it should already exist on the server)
-        cache = ignite.cache(cacheName);
-        if (cache == null) {
-            // If not found, create it (shouldn't happen if server started 
first)
-            LOG.warn("Cache {} not found on server, creating it", cacheName);
-            CacheConfiguration<String, ExtensionConfigDTO> cacheCfg = new 
CacheConfiguration<>(cacheName);
-            cacheCfg.setCacheMode(cacheMode);
-            cacheCfg.setBackups(cacheMode == CacheMode.PARTITIONED ? 1 : 0);
-            cache = ignite.getOrCreateCache(cacheCfg);
+        LOG.info("Initializing IgniteConfigStore with table: {}, replicas: {}, 
partitions: {}, instance: {}",
+                tableName, replicas, partitions, igniteInstanceName);
+
+        try {
+            ignite = org.apache.ignite.client.IgniteClient.builder()
+                    .addresses("127.0.0.1:10800")
+                    .build();
+
+            Table table = ignite.tables().table(tableName);
+            if (table == null) {
+                throw new IllegalStateException("Table " + tableName + " not 
found. Ensure IgniteStoreServer is running.");
+            }
+
+            kvView = table.keyValueView(String.class, 
ExtensionConfigDTO.class);
+
+            LOG.info("IgniteConfigStore initialized successfully");
+        } catch (Exception e) {
+            LOG.error("Failed to initialize IgniteConfigStore", e);
+            throw new TikaConfigException("Failed to connect to Ignite 
cluster. Ensure IgniteStoreServer is running.", e);
         }
-        LOG.info("IgniteConfigStore initialized successfully as client");
     }
 
     @Override
     public void put(String id, ExtensionConfig config) {
-        if (cache == null) {
-            throw new IllegalStateException("IgniteConfigStore not 
initialized. Call init() first.");
+        checkInitialized();
+        try {
+            kvView.put(null, id, new ExtensionConfigDTO(config.name(), 
config.json()));
+        } catch (Exception e) {
+            LOG.error("Failed to put config with id: {}", id, e);
+            throw new RuntimeException("Failed to put config", e);
         }
-        cache.put(id, new ExtensionConfigDTO(config));
     }
 
     @Override
     public ExtensionConfig get(String id) {
-        if (cache == null) {
-            throw new IllegalStateException("IgniteConfigStore not 
initialized. Call init() first.");
+        checkInitialized();
+        try {
+            ExtensionConfigDTO dto = kvView.get(null, id);
+            return dto != null ? new ExtensionConfig(id, dto.getName(), 
dto.getJson()) : null;
+        } catch (Exception e) {
+            LOG.error("Failed to get config with id: {}", id, e);
+            throw new RuntimeException("Failed to get config", e);
         }
-        ExtensionConfigDTO dto = cache.get(id);
-        return dto != null ? dto.toExtensionConfig() : null;
     }
 
     @Override
     public boolean containsKey(String id) {
-        if (cache == null) {
-            throw new IllegalStateException("IgniteConfigStore not 
initialized. Call init() first.");
+        checkInitialized();
+        try {
+            return kvView.get(null, id) != null;
+        } catch (Exception e) {
+            LOG.error("Failed to check if key exists: {}", id, e);
+            throw new RuntimeException("Failed to check key", e);
         }
-        return cache.containsKey(id);
     }
 
     @Override
     public Set<String> keySet() {
-        if (cache == null) {
-            throw new IllegalStateException("IgniteConfigStore not 
initialized. Call init() first.");
+        checkInitialized();
+        try {
+            var resultSet = ignite.sql().execute(null, "SELECT id FROM " + 
tableName);
+            Set<String> keys = new HashSet<>();
+            while (resultSet.hasNext()) {
+                keys.add(resultSet.next().stringValue("id"));
+            }
+            return keys;
+        } catch (Exception e) {
+            LOG.error("Failed to get keySet", e);
+            throw new RuntimeException("Failed to get keySet", e);
         }
-        return Set.copyOf(cache.query(new 
org.apache.ignite.cache.query.ScanQuery<String, ExtensionConfigDTO>())
-                .getAll()
-                .stream()
-                .map(javax.cache.Cache.Entry::getKey)
-                .toList());
     }
 
     @Override
     public int size() {
-        if (cache == null) {
-            throw new IllegalStateException("IgniteConfigStore not 
initialized. Call init() first.");
+        checkInitialized();
+        try {
+            var resultSet = ignite.sql().execute(null, "SELECT COUNT(*) as cnt 
FROM " + tableName);
+            if (resultSet.hasNext()) {
+                return (int) resultSet.next().longValue("cnt");
+            }
+            return 0;
+        } catch (Exception e) {
+            LOG.error("Failed to get size", e);
+            throw new RuntimeException("Failed to get size", e);
         }
-        return cache.size();
     }
 
     @Override
     public ExtensionConfig remove(String id) {
-        if (cache == null) {
-            throw new IllegalStateException("IgniteConfigStore not 
initialized. Call init() first.");
+        checkInitialized();
+        try {
+            ExtensionConfigDTO removed = kvView.getAndRemove(null, id);
+            return removed != null ? new ExtensionConfig(id, 
removed.getName(), removed.getJson()) : null;
+        } catch (Exception e) {
+            LOG.error("Failed to remove config with id: {}", id, e);
+            throw new RuntimeException("Failed to remove config", e);
         }
-        ExtensionConfigDTO removed = cache.getAndRemove(id);
-        return removed != null ? removed.toExtensionConfig() : null;
     }
 
     public void close() {
         if (ignite != null && autoClose) {
             LOG.info("Closing IgniteConfigStore");
-            ignite.close();
+            try {
+                ((AutoCloseable) ignite).close();
+            } catch (Exception e) {
+                LOG.error("Error closing Ignite", e);
+            }
             ignite = null;
-            cache = null;
+            kvView = null;
             closed = true;
         }
     }
 
-    public void setCacheName(String cacheName) {
-        this.cacheName = cacheName;
+    public void setTableName(String tableName) {
+        this.tableName = tableName;
     }
 
-    public void setCacheMode(CacheMode cacheMode) {
-        this.cacheMode = cacheMode;
+    public void setReplicas(int replicas) {
+        this.replicas = replicas;
+    }
+
+    public void setPartitions(int partitions) {
+        this.partitions = partitions;
     }
 
     public void setIgniteInstanceName(String igniteInstanceName) {
@@ -203,7 +221,9 @@ public class IgniteConfigStore implements ConfigStore {
         this.autoClose = autoClose;
     }
 
-    public void setClientMode(boolean clientMode) {
-        this.clientMode = clientMode;
+    private void checkInitialized() {
+        if (kvView == null) {
+            throw new IllegalStateException("IgniteConfigStore not 
initialized. Call init() first.");
+        }
     }
 }
diff --git 
a/tika-pipes/tika-pipes-config-store-ignite/src/main/java/org/apache/tika/pipes/ignite/config/IgniteConfigStoreConfig.java
 
b/tika-pipes/tika-pipes-config-store-ignite/src/main/java/org/apache/tika/pipes/ignite/config/IgniteConfigStoreConfig.java
index 6ec7699ec7..177990df3f 100644
--- 
a/tika-pipes/tika-pipes-config-store-ignite/src/main/java/org/apache/tika/pipes/ignite/config/IgniteConfigStoreConfig.java
+++ 
b/tika-pipes/tika-pipes-config-store-ignite/src/main/java/org/apache/tika/pipes/ignite/config/IgniteConfigStoreConfig.java
@@ -18,18 +18,17 @@ package org.apache.tika.pipes.ignite.config;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
-import org.apache.ignite.cache.CacheMode;
 
 import org.apache.tika.exception.TikaConfigException;
 
 /**
- * Configuration for IgniteConfigStore.
- * 
- * Example JSON configuration:
+ * Configuration for {@link org.apache.tika.pipes.ignite.IgniteConfigStore}.
+ *
+ * <p>Example JSON configuration:
  * <pre>
  * {
- *   "cacheName": "my-tika-cache",
- *   "cacheMode": "REPLICATED",
+ *   "tableName": "my-tika-table",
+ *   "replicas": 2,
  *   "igniteInstanceName": "MyTikaCluster",
  *   "autoClose": true
  * }
@@ -50,47 +49,30 @@ public class IgniteConfigStoreConfig {
         }
     }
 
-    private String cacheName = "tika-config-store";
-    private String cacheMode = "REPLICATED";
+    private String tableName = "tika_config_store";
+    private int replicas = 2;
     private String igniteInstanceName = "TikaIgniteConfigStore";
     private boolean autoClose = true;
+    private int partitions = 10;
 
-    public String getCacheName() {
-        return cacheName;
+    public String getTableName() {
+        return tableName;
     }
 
-    public IgniteConfigStoreConfig setCacheName(String cacheName) {
-        this.cacheName = cacheName;
+    public IgniteConfigStoreConfig setTableName(String tableName) {
+        this.tableName = tableName;
         return this;
     }
 
-    public String getCacheMode() {
-        return cacheMode;
+    public int getReplicas() {
+        return replicas;
     }
 
-    public IgniteConfigStoreConfig setCacheMode(String cacheMode) {
-        this.cacheMode = cacheMode;
+    public IgniteConfigStoreConfig setReplicas(int replicas) {
+        this.replicas = replicas;
         return this;
     }
 
-    public CacheMode getCacheModeEnum() {
-        if (cacheMode == null || cacheMode.trim().isEmpty()) {
-            return CacheMode.REPLICATED;
-        }
-        
-        if ("PARTITIONED".equalsIgnoreCase(cacheMode)) {
-            return CacheMode.PARTITIONED;
-        }
-        
-        if ("REPLICATED".equalsIgnoreCase(cacheMode)) {
-            return CacheMode.REPLICATED;
-        }
-        
-        throw new IllegalArgumentException(
-                "Unsupported cacheMode: '" + cacheMode
-                        + "'. Supported values are PARTITIONED and 
REPLICATED.");
-    }
-
     public String getIgniteInstanceName() {
         return igniteInstanceName;
     }
@@ -108,4 +90,13 @@ public class IgniteConfigStoreConfig {
         this.autoClose = autoClose;
         return this;
     }
+
+    public int getPartitions() {
+        return partitions;
+    }
+
+    public IgniteConfigStoreConfig setPartitions(int partitions) {
+        this.partitions = partitions;
+        return this;
+    }
 }
diff --git 
a/tika-pipes/tika-pipes-config-store-ignite/src/main/java/org/apache/tika/pipes/ignite/server/IgniteStoreServer.java
 
b/tika-pipes/tika-pipes-config-store-ignite/src/main/java/org/apache/tika/pipes/ignite/server/IgniteStoreServer.java
index 8d8759ac83..19dc38dab1 100644
--- 
a/tika-pipes/tika-pipes-config-store-ignite/src/main/java/org/apache/tika/pipes/ignite/server/IgniteStoreServer.java
+++ 
b/tika-pipes/tika-pipes-config-store-ignite/src/main/java/org/apache/tika/pipes/ignite/server/IgniteStoreServer.java
@@ -16,103 +16,131 @@
  */
 package org.apache.tika.pipes.ignite.server;
 
-import org.apache.ignite.Ignite;
-import org.apache.ignite.IgniteCache;
-import org.apache.ignite.Ignition;
-import org.apache.ignite.cache.CacheMode;
-import org.apache.ignite.configuration.CacheConfiguration;
-import org.apache.ignite.configuration.IgniteConfiguration;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Locale;
+
+import org.apache.ignite.IgniteServer;
+import org.apache.ignite.InitParameters;
+import org.apache.ignite.table.Table;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import org.apache.tika.pipes.ignite.ExtensionConfigDTO;
-
 /**
- * Embedded Ignite server that hosts the distributed cache.
- * This runs as a background thread within the tika-grpc process.
- * Tika gRPC and forked PipesServer instances connect as clients.
+ * Embedded Ignite 3.x server node that hosts the config store table.
+ * The {@link org.apache.tika.pipes.ignite.IgniteConfigStore} connects to this 
node as a thin client.
  */
 public class IgniteStoreServer implements AutoCloseable {
-    
+
     private static final Logger LOG = 
LoggerFactory.getLogger(IgniteStoreServer.class);
-    private static final String DEFAULT_CACHE_NAME = "tika-config-store";
-    private static final String DEFAULT_INSTANCE_NAME = "TikaIgniteServer";
-    
-    private Ignite ignite;
-    private final String cacheName;
-    private final CacheMode cacheMode;
-    private final String instanceName;
-    
+    private static final String DEFAULT_TABLE_NAME = "tika_config_store";
+    private static final String DEFAULT_NODE_NAME = "TikaIgniteServer";
+
+    private IgniteServer node;
+    private final String tableName;
+    private final String nodeName;
+    private final Path workDir;
+
     public IgniteStoreServer() {
-        this(DEFAULT_CACHE_NAME, CacheMode.REPLICATED, DEFAULT_INSTANCE_NAME);
+        this(DEFAULT_TABLE_NAME, DEFAULT_NODE_NAME);
     }
-    
-    public IgniteStoreServer(String cacheName, CacheMode cacheMode, String 
instanceName) {
-        this.cacheName = cacheName;
-        this.cacheMode = cacheMode;
-        this.instanceName = instanceName;
+
+    public IgniteStoreServer(String tableName, String nodeName) {
+        this.tableName = tableName;
+        this.nodeName = nodeName;
+        this.workDir = Paths.get(System.getProperty("ignite.work.dir", 
"/var/cache/tika/ignite-work"));
     }
-    
+
     /**
-     * Start the Ignite server node in a background daemon thread.
+     * Start the Ignite server node and initialize the cluster synchronously.
      */
-    public void startAsync() {
-        Thread serverThread = new Thread(() -> {
-            try {
-                start();
-            } catch (Exception e) {
-                LOG.error("Failed to start Ignite server", e);
-            }
-        }, "IgniteServerThread");
-        serverThread.setDaemon(true);
-        serverThread.start();
-        
-        // Wait for server to initialize
-        try {
-            Thread.sleep(3000);
-        } catch (InterruptedException e) {
-            Thread.currentThread().interrupt();
+    public void start() throws Exception {
+        LOG.info("Starting Ignite 3.x server: node={}, table={}, workDir={}",
+                nodeName, tableName, workDir);
+
+        if (Files.exists(workDir)) {
+            deleteDirectory(workDir);
         }
+        Files.createDirectories(workDir);
+
+        Path configPath = workDir.resolve("ignite-config.conf");
+        String config = """
+                ignite {
+                  network {
+                    port = 3344
+                    nodeFinder {
+                      netClusterNodes = [ "localhost:3344" ]
+                    }
+                  }
+                  clientConnector {
+                    port = 10800
+                  }
+                }
+                """;
+        Files.writeString(configPath, config);
+
+        node = IgniteServer.start(nodeName, configPath, workDir);
+
+        InitParameters initParameters = InitParameters.builder()
+                .clusterName("tika-cluster")
+                .metaStorageNodes(node)
+                .build();
+        node.initClusterAsync(initParameters).get();
+
+        Thread.sleep(2000);
+
+        createTable();
+
+        LOG.info("Ignite server is ready");
     }
-    
-    private void start() throws Exception {
-        LOG.info("Starting Ignite server: instance={}, cache={}, mode={}", 
-            instanceName, cacheName, cacheMode);
-        
-        // Disable Ignite's Object Input Filter autoconfiguration to avoid 
conflicts
-        
System.setProperty("IGNITE_ENABLE_OBJECT_INPUT_FILTER_AUTOCONFIGURATION", 
"false");
-        
-        IgniteConfiguration cfg = new IgniteConfiguration();
-        cfg.setIgniteInstanceName(instanceName);
-        cfg.setClientMode(false); // Server mode
-        cfg.setPeerClassLoadingEnabled(false); // Disable to avoid classloader 
conflicts
-        
-        // Set work directory to /var/cache/tika to match Tika's cache location
-        cfg.setWorkDirectory(System.getProperty("ignite.work.dir", 
"/var/cache/tika/ignite-work"));
-        
-        ignite = Ignition.start(cfg);
-        
-        CacheConfiguration<String, ExtensionConfigDTO> cacheCfg = 
-            new CacheConfiguration<>(cacheName);
-        cacheCfg.setCacheMode(cacheMode);
-        cacheCfg.setBackups(cacheMode == CacheMode.PARTITIONED ? 1 : 0);
-        
-        IgniteCache<String, ExtensionConfigDTO> cache = 
ignite.getOrCreateCache(cacheCfg);
-        
-        LOG.info("Ignite server started successfully with cache: {}", 
cache.getName());
-        LOG.info("Ignite topology: {} nodes", ignite.cluster().nodes().size());
+
+    private void createTable() {
+        try {
+            Table existingTable = node.api().tables().table(tableName);
+            if (existingTable != null) {
+                LOG.info("Table {} already exists", tableName);
+                return;
+            }
+
+            String createTableSql = String.format(Locale.ROOT,
+                    "CREATE TABLE IF NOT EXISTS %s (" +
+                    "  id VARCHAR PRIMARY KEY," +
+                    "  name VARCHAR," +
+                    "  json VARCHAR(10000)" +
+                    ")",
+                    tableName);
+
+            node.api().sql().execute(null, createTableSql);
+            LOG.info("Table {} created successfully", tableName);
+        } catch (Exception e) {
+            LOG.error("Failed to create table: {}", tableName, e);
+            throw new RuntimeException("Failed to create table", e);
+        }
     }
-    
+
     public boolean isRunning() {
-        return ignite != null;
+        return node != null;
     }
-    
+
+    private void deleteDirectory(Path dir) throws Exception {
+        Files.walk(dir)
+                .sorted((a, b) -> b.compareTo(a))
+                .forEach(path -> {
+                    try {
+                        Files.delete(path);
+                    } catch (Exception e) {
+                        LOG.warn("Failed to delete {}", path, e);
+                    }
+                });
+    }
+
     @Override
     public void close() {
-        if (ignite != null) {
-            LOG.info("Stopping Ignite server: {}", instanceName);
-            ignite.close();
-            ignite = null;
+        if (node != null) {
+            LOG.info("Stopping Ignite server: {}", nodeName);
+            node.shutdown();
+            node = null;
         }
     }
 }
diff --git 
a/tika-pipes/tika-pipes-config-store-ignite/src/test/java/org/apache/tika/pipes/ignite/IgniteConfigStoreTest.java
 
b/tika-pipes/tika-pipes-config-store-ignite/src/test/java/org/apache/tika/pipes/ignite/IgniteConfigStoreTest.java
index 0e02a2c7e9..f55a9a5145 100644
--- 
a/tika-pipes/tika-pipes-config-store-ignite/src/test/java/org/apache/tika/pipes/ignite/IgniteConfigStoreTest.java
+++ 
b/tika-pipes/tika-pipes-config-store-ignite/src/test/java/org/apache/tika/pipes/ignite/IgniteConfigStoreTest.java
@@ -22,39 +22,59 @@ import static 
org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.junit.jupiter.api.Assumptions.assumeTrue;
 
 import java.nio.file.Path;
+import java.util.HashSet;
+import java.util.Set;
 
+import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.io.TempDir;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
+import org.apache.tika.pipes.ignite.server.IgniteStoreServer;
 import org.apache.tika.plugins.ExtensionConfig;
 
+/**
+ * Integration tests for {@link IgniteConfigStore} using an embedded Ignite 
3.x server.
+ */
 public class IgniteConfigStoreTest {
 
+    private static final Logger LOG = 
LoggerFactory.getLogger(IgniteConfigStoreTest.class);
+
     @TempDir
-    private Path tempDir;
-    
+    private static Path workDir;
+
+    private static IgniteStoreServer server;
     private IgniteConfigStore store;
 
+    @BeforeAll
+    public static void setUpServer() throws Exception {
+        System.setProperty("ignite.work.dir", workDir.toString());
+        server = new IgniteStoreServer();
+        server.start();
+        LOG.info("Ignite server started");
+    }
+
+    @AfterAll
+    public static void tearDownServer() {
+        if (server != null) {
+            server.close();
+        }
+    }
+
     @BeforeEach
     public void setUp() throws Exception {
-        // Set the work directory for Ignite to use the temp directory
-        System.setProperty("ignite.work.dir", tempDir.toString());
-        
         store = new IgniteConfigStore();
-        store.setIgniteInstanceName("TestIgniteInstance-" + 
System.currentTimeMillis());
-        store.setClientMode(false);  // Run as server for tests
-        try {
-            store.init();
-        } catch (Exception e) {
-            // Ignite can fail to start on machines with many network 
interfaces because
-            // it constructs a work directory path from all interface 
addresses, which
-            // can exceed the OS path length limit. Skip rather than fail.
-            assumeTrue(false, "Ignite failed to start (likely path-length 
limit): " + e.getMessage());
+        store.init();
+        Set<String> keysToRemove = new HashSet<>(store.keySet());
+        for (String key : keysToRemove) {
+            store.remove(key);
         }
     }
 
@@ -68,9 +88,8 @@ public class IgniteConfigStoreTest {
     @Test
     public void testPutAndGet() {
         ExtensionConfig config = new ExtensionConfig("id1", "type1", 
"{\"key\":\"value\"}");
-        
         store.put("id1", config);
-        
+
         ExtensionConfig retrieved = store.get("id1");
         assertNotNull(retrieved);
         assertEquals("id1", retrieved.id());
@@ -80,12 +99,8 @@ public class IgniteConfigStoreTest {
 
     @Test
     public void testContainsKey() {
-        ExtensionConfig config = new ExtensionConfig("id1", "type1", "{}");
-        
         assertFalse(store.containsKey("id1"));
-        
-        store.put("id1", config);
-        
+        store.put("id1", new ExtensionConfig("id1", "type1", "{}"));
         assertTrue(store.containsKey("id1"));
         assertFalse(store.containsKey("nonexistent"));
     }
@@ -93,13 +108,10 @@ public class IgniteConfigStoreTest {
     @Test
     public void testSize() {
         assertEquals(0, store.size());
-        
         store.put("id1", new ExtensionConfig("id1", "type1", "{}"));
         assertEquals(1, store.size());
-        
         store.put("id2", new ExtensionConfig("id2", "type2", "{}"));
         assertEquals(2, store.size());
-        
         store.put("id1", new ExtensionConfig("id1", "type1", 
"{\"updated\":true}"));
         assertEquals(2, store.size());
     }
@@ -107,10 +119,8 @@ public class IgniteConfigStoreTest {
     @Test
     public void testKeySet() {
         assertTrue(store.keySet().isEmpty());
-        
         store.put("id1", new ExtensionConfig("id1", "type1", "{}"));
         store.put("id2", new ExtensionConfig("id2", "type2", "{}"));
-        
         assertEquals(2, store.keySet().size());
         assertTrue(store.keySet().contains("id1"));
         assertTrue(store.keySet().contains("id2"));
@@ -124,13 +134,9 @@ public class IgniteConfigStoreTest {
 
     @Test
     public void testUpdateExisting() {
-        ExtensionConfig config1 = new ExtensionConfig("id1", "type1", 
"{\"version\":1}");
-        ExtensionConfig config2 = new ExtensionConfig("id1", "type1", 
"{\"version\":2}");
-        
-        store.put("id1", config1);
+        store.put("id1", new ExtensionConfig("id1", "type1", 
"{\"version\":1}"));
         assertEquals("{\"version\":1}", store.get("id1").json());
-        
-        store.put("id1", config2);
+        store.put("id1", new ExtensionConfig("id1", "type1", 
"{\"version\":2}"));
         assertEquals("{\"version\":2}", store.get("id1").json());
         assertEquals(1, store.size());
     }
@@ -139,12 +145,9 @@ public class IgniteConfigStoreTest {
     public void testMultipleConfigs() {
         for (int i = 0; i < 10; i++) {
             String id = "config" + i;
-            ExtensionConfig config = new ExtensionConfig(id, "type" + i, 
"{\"index\":" + i + "}");
-            store.put(id, config);
+            store.put(id, new ExtensionConfig(id, "type" + i, "{\"index\":" + 
i + "}"));
         }
-        
         assertEquals(10, store.size());
-        
         for (int i = 0; i < 10; i++) {
             String id = "config" + i;
             ExtensionConfig config = store.get(id);
@@ -157,65 +160,43 @@ public class IgniteConfigStoreTest {
     @Test
     public void testUninitializedStore() {
         IgniteConfigStore uninitializedStore = new IgniteConfigStore();
-        
-        assertThrows(IllegalStateException.class, () -> {
-            uninitializedStore.put("id1", new ExtensionConfig("id1", "type1", 
"{}"));
-        });
-        
-        assertThrows(IllegalStateException.class, () -> {
-            uninitializedStore.get("id1");
-        });
-        
-        assertThrows(IllegalStateException.class, () -> {
-            uninitializedStore.containsKey("id1");
-        });
-        
-        assertThrows(IllegalStateException.class, () -> {
-            uninitializedStore.size();
-        });
-        
-        assertThrows(IllegalStateException.class, () -> {
-            uninitializedStore.keySet();
-        });
+        assertThrows(IllegalStateException.class, () -> 
uninitializedStore.put("id1", new ExtensionConfig("id1", "type1", "{}")));
+        assertThrows(IllegalStateException.class, () -> 
uninitializedStore.get("id1"));
+        assertThrows(IllegalStateException.class, () -> 
uninitializedStore.containsKey("id1"));
+        assertThrows(IllegalStateException.class, () -> 
uninitializedStore.size());
+        assertThrows(IllegalStateException.class, () -> 
uninitializedStore.keySet());
     }
 
     @Test
     public void testThreadSafety() throws InterruptedException {
         int numThreads = 10;
         int numOperationsPerThread = 100;
-        
+
         Thread[] threads = new Thread[numThreads];
         for (int i = 0; i < numThreads; i++) {
             final int threadId = i;
             threads[i] = new Thread(() -> {
                 for (int j = 0; j < numOperationsPerThread; j++) {
                     String id = "thread" + threadId + "_config" + j;
-                    ExtensionConfig config = new ExtensionConfig(id, "type", 
"{}");
-                    store.put(id, config);
+                    store.put(id, new ExtensionConfig(id, "type", "{}"));
                     assertNotNull(store.get(id));
                 }
             });
             threads[i].start();
         }
-        
         for (Thread thread : threads) {
             thread.join();
         }
-        
         assertEquals(numThreads * numOperationsPerThread, store.size());
     }
 
     @Test
+    @Disabled("Custom table names require server-side table creation - not yet 
implemented")
     public void testCustomCacheName() throws Exception {
-        IgniteConfigStore customStore = new IgniteConfigStore("custom-cache");
-        customStore.setIgniteInstanceName("CustomInstance-" + 
System.currentTimeMillis());
-        
+        IgniteConfigStore customStore = new IgniteConfigStore("custom_table");
         try {
             customStore.init();
-            
-            ExtensionConfig config = new ExtensionConfig("id1", "type1", "{}");
-            customStore.put("id1", config);
-            
+            customStore.put("id1", new ExtensionConfig("id1", "type1", "{}"));
             assertNotNull(customStore.get("id1"));
             assertEquals("id1", customStore.get("id1").id());
         } finally {
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 e60f1f5c84..fdf205ef4d 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
@@ -101,6 +101,11 @@ class EmitHandler {
     private PipesResult emit(String taskId, EmitKey emitKey,
                       boolean isExtractEmbeddedBytes, 
MetadataListAndEmbeddedBytes parseData,
                       String parseExceptionStack, ParseContext parseContext) {
+        if (emitKey == EmitKey.NO_EMIT || emitKey.getEmitterId() == null) {
+            LOG.debug("No emitter specified for task id '{}', skipping 
emission", taskId);
+            return new PipesResult(PipesResult.RESULT_STATUS.PARSE_SUCCESS);
+        }
+
         Emitter emitter = null;
 
         try {

Reply via email to