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

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


The following commit(s) were added to refs/heads/main by this push:
     new 1d10bd679d do not emit empty row if all files are empty, fixes #8071 
(#8091)
1d10bd679d is described below

commit 1d10bd679da7ae418ed8dff7c395888bad0c27c0
Author: Hans Van Akelyen <[email protected]>
AuthorDate: Tue Aug 25 15:23:17 2026 +0200

    do not emit empty row if all files are empty, fixes #8071 (#8091)
---
 .../json/0013-json-input-ignore-empty-file.hpl     | 550 +++++++++++++++++++++
 .../json/0014-json-input-fail-on-empty-file.hpl    | 123 +++++
 .../main-0013-json-input-ignore-empty-file.hwf     | 149 ++++++
 .../main-0014-json-input-fail-on-empty-file.hwf    | 168 +++++++
 .../resources/json-input-ignore-empty-file-it.json |   7 +
 .../pipeline/transforms/jsoninput/JsonInput.java   |  28 +-
 .../transforms/jsoninput/reader/InputsReader.java  |   9 +-
 .../transforms/jsoninput/JsonInputTest.java        |  94 ++++
 8 files changed, 1124 insertions(+), 4 deletions(-)

diff --git a/integration-tests/json/0013-json-input-ignore-empty-file.hpl 
b/integration-tests/json/0013-json-input-ignore-empty-file.hpl
new file mode 100644
index 0000000000..a2e96fd742
--- /dev/null
+++ b/integration-tests/json/0013-json-input-ignore-empty-file.hpl
@@ -0,0 +1,550 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+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.
+
+-->
+<pipeline>
+  <info>
+    <name>0013-json-input-ignore-empty-file</name>
+    <name_sync_with_filename>Y</name_sync_with_filename>
+    <description>Issue #8071: a zero byte file that is ignored must not 
produce an output row</description>
+    <extended_description/>
+    <pipeline_version/>
+    <pipeline_type>Normal</pipeline_type>
+    <parameters>
+    </parameters>
+    <capture_transform_performance>N</capture_transform_performance>
+    
<transform_performance_capturing_delay>1000</transform_performance_capturing_delay>
+    
<transform_performance_capturing_size_limit>100</transform_performance_capturing_size_limit>
+    <created_user>-</created_user>
+    <created_date>2026/08/25 10:00:00.000</created_date>
+    <modified_user>-</modified_user>
+    <modified_date>2026/08/25 10:00:00.000</modified_date>
+  </info>
+  <notepads>
+  </notepads>
+  <order>
+    <hop>
+      <from>Read only the empty file</from>
+      <to>Empty file emitted a row - Abort</to>
+      <enabled>Y</enabled>
+    </hop>
+    <hop>
+      <from>Read good then empty file</from>
+      <to>Count rows of good then empty</to>
+      <enabled>Y</enabled>
+    </hop>
+    <hop>
+      <from>Count rows of good then empty</from>
+      <to>Check good then empty</to>
+      <enabled>Y</enabled>
+    </hop>
+    <hop>
+      <from>Check good then empty</from>
+      <to>Good then empty OK</to>
+      <enabled>Y</enabled>
+    </hop>
+    <hop>
+      <from>Check good then empty</from>
+      <to>Good then empty wrong count - Abort</to>
+      <enabled>Y</enabled>
+    </hop>
+    <hop>
+      <from>Read empty then good file</from>
+      <to>Count rows of empty then good</to>
+      <enabled>Y</enabled>
+    </hop>
+    <hop>
+      <from>Count rows of empty then good</from>
+      <to>Check empty then good</to>
+      <enabled>Y</enabled>
+    </hop>
+    <hop>
+      <from>Check empty then good</from>
+      <to>Empty then good OK</to>
+      <enabled>Y</enabled>
+    </hop>
+    <hop>
+      <from>Check empty then good</from>
+      <to>Empty then good wrong count - Abort</to>
+      <enabled>Y</enabled>
+    </hop>
+  </order>
+  <transform>
+    <name>Read only the empty file</name>
+    <type>JsonInput</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <include>N</include>
+    <include_field/>
+    <rownum>N</rownum>
+    <rownum_field/>
+    <limit>0</limit>
+    <valueField/>
+    <IsInFields>N</IsInFields>
+    <IsAFile>N</IsAFile>
+    <addresultfile>N</addresultfile>
+    <IsIgnoreEmptyFile>Y</IsIgnoreEmptyFile>
+    <doNotFailIfNoFile>N</doNotFailIfNoFile>
+    <ignoreMissingPath>N</ignoreMissingPath>
+    <readurl>N</readurl>
+    <removeSourceField>N</removeSourceField>
+    <defaultPathLeafToNull>Y</defaultPathLeafToNull>
+    <files>
+      <file>
+        <name>${java.io.tmpdir}/hop-it-0013-empty.json</name>
+        <filemask/>
+        <exclude_filemask/>
+        <file_required>N</file_required>
+        <include_subfolders>N</include_subfolders>
+      </file>
+    </files>
+    <fields>
+      <field>
+        <name>name</name>
+        <path>$.fighters[*].name</path>
+        <type>String</type>
+        <format/>
+        <currency/>
+        <decimal/>
+        <group/>
+        <length>-1</length>
+        <precision>-1</precision>
+        <trim_type>none</trim_type>
+        <repeat>N</repeat>
+      </field>
+      <field>
+        <name>power</name>
+        <path>$.fighters[*].power</path>
+        <type>Integer</type>
+        <format/>
+        <currency/>
+        <decimal/>
+        <group/>
+        <length>-1</length>
+        <precision>-1</precision>
+        <trim_type>none</trim_type>
+        <repeat>N</repeat>
+      </field>
+    </fields>
+    <attributes/>
+    <GUI>
+      <xloc>160</xloc>
+      <yloc>96</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>Empty file emitted a row - Abort</name>
+    <type>Abort</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <abort_option>ABORT_WITH_ERROR</abort_option>
+    <always_log_rows>Y</always_log_rows>
+    <message>The ignored empty file produced an output row</message>
+    <row_threshold>0</row_threshold>
+    <attributes/>
+    <GUI>
+      <xloc>384</xloc>
+      <yloc>96</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>Read good then empty file</name>
+    <type>JsonInput</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <include>N</include>
+    <include_field/>
+    <rownum>N</rownum>
+    <rownum_field/>
+    <limit>0</limit>
+    <valueField/>
+    <IsInFields>N</IsInFields>
+    <IsAFile>N</IsAFile>
+    <addresultfile>N</addresultfile>
+    <IsIgnoreEmptyFile>Y</IsIgnoreEmptyFile>
+    <doNotFailIfNoFile>N</doNotFailIfNoFile>
+    <ignoreMissingPath>N</ignoreMissingPath>
+    <readurl>N</readurl>
+    <removeSourceField>N</removeSourceField>
+    <defaultPathLeafToNull>Y</defaultPathLeafToNull>
+    <files>
+      <file>
+        
<name>${PROJECT_HOME}/resources/json-input-ignore-empty-file-it.json</name>
+        <filemask/>
+        <exclude_filemask/>
+        <file_required>N</file_required>
+        <include_subfolders>N</include_subfolders>
+      </file>
+      <file>
+        <name>${java.io.tmpdir}/hop-it-0013-empty.json</name>
+        <filemask/>
+        <exclude_filemask/>
+        <file_required>N</file_required>
+        <include_subfolders>N</include_subfolders>
+      </file>
+    </files>
+    <fields>
+      <field>
+        <name>name</name>
+        <path>$.fighters[*].name</path>
+        <type>String</type>
+        <format/>
+        <currency/>
+        <decimal/>
+        <group/>
+        <length>-1</length>
+        <precision>-1</precision>
+        <trim_type>none</trim_type>
+        <repeat>N</repeat>
+      </field>
+      <field>
+        <name>power</name>
+        <path>$.fighters[*].power</path>
+        <type>Integer</type>
+        <format/>
+        <currency/>
+        <decimal/>
+        <group/>
+        <length>-1</length>
+        <precision>-1</precision>
+        <trim_type>none</trim_type>
+        <repeat>N</repeat>
+      </field>
+    </fields>
+    <attributes/>
+    <GUI>
+      <xloc>160</xloc>
+      <yloc>240</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>Count rows of good then empty</name>
+    <type>GroupBy</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <all_rows>N</all_rows>
+    <ignore_aggregate>N</ignore_aggregate>
+    <field_ignore/>
+    <directory>${java.io.tmpdir}</directory>
+    <prefix>grp</prefix>
+    <add_linenr>N</add_linenr>
+    <linenr_fieldname/>
+    <give_back_row>N</give_back_row>
+    <group>
+      </group>
+    <fields>
+      <field>
+        <aggregate>num_rows</aggregate>
+        <subject/>
+        <type>COUNT_ANY</type>
+        <valuefield/>
+      </field>
+    </fields>
+    <attributes/>
+    <GUI>
+      <xloc>384</xloc>
+      <yloc>240</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>Check good then empty</name>
+    <type>FilterRows</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <send_true_to>Good then empty OK</send_true_to>
+    <send_false_to>Good then empty wrong count - Abort</send_false_to>
+    <compare>
+      <condition>
+        <negated>N</negated>
+        <leftvalue>num_rows</leftvalue>
+        <function>=</function>
+        <rightvalue/>
+        <value>
+          <name>constant</name>
+          <type>Integer</type>
+          <text>3</text>
+          <length>-1</length>
+          <precision>0</precision>
+          <isnull>N</isnull>
+          <mask>####0;-####0</mask>
+        </value>
+      </condition>
+    </compare>
+    <attributes/>
+    <GUI>
+      <xloc>592</xloc>
+      <yloc>240</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>Good then empty OK</name>
+    <type>Dummy</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <attributes/>
+    <GUI>
+      <xloc>816</xloc>
+      <yloc>240</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>Good then empty wrong count - Abort</name>
+    <type>Abort</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <abort_option>ABORT_WITH_ERROR</abort_option>
+    <always_log_rows>Y</always_log_rows>
+    <message>Reading a good file followed by an ignored empty file did not 
return 3 rows</message>
+    <row_threshold>0</row_threshold>
+    <attributes/>
+    <GUI>
+      <xloc>592</xloc>
+      <yloc>352</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>Read empty then good file</name>
+    <type>JsonInput</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <include>N</include>
+    <include_field/>
+    <rownum>N</rownum>
+    <rownum_field/>
+    <limit>0</limit>
+    <valueField/>
+    <IsInFields>N</IsInFields>
+    <IsAFile>N</IsAFile>
+    <addresultfile>N</addresultfile>
+    <IsIgnoreEmptyFile>Y</IsIgnoreEmptyFile>
+    <doNotFailIfNoFile>N</doNotFailIfNoFile>
+    <ignoreMissingPath>N</ignoreMissingPath>
+    <readurl>N</readurl>
+    <removeSourceField>N</removeSourceField>
+    <defaultPathLeafToNull>Y</defaultPathLeafToNull>
+    <files>
+      <file>
+        <name>${java.io.tmpdir}/hop-it-0013-empty.json</name>
+        <filemask/>
+        <exclude_filemask/>
+        <file_required>N</file_required>
+        <include_subfolders>N</include_subfolders>
+      </file>
+      <file>
+        
<name>${PROJECT_HOME}/resources/json-input-ignore-empty-file-it.json</name>
+        <filemask/>
+        <exclude_filemask/>
+        <file_required>N</file_required>
+        <include_subfolders>N</include_subfolders>
+      </file>
+    </files>
+    <fields>
+      <field>
+        <name>name</name>
+        <path>$.fighters[*].name</path>
+        <type>String</type>
+        <format/>
+        <currency/>
+        <decimal/>
+        <group/>
+        <length>-1</length>
+        <precision>-1</precision>
+        <trim_type>none</trim_type>
+        <repeat>N</repeat>
+      </field>
+      <field>
+        <name>power</name>
+        <path>$.fighters[*].power</path>
+        <type>Integer</type>
+        <format/>
+        <currency/>
+        <decimal/>
+        <group/>
+        <length>-1</length>
+        <precision>-1</precision>
+        <trim_type>none</trim_type>
+        <repeat>N</repeat>
+      </field>
+    </fields>
+    <attributes/>
+    <GUI>
+      <xloc>160</xloc>
+      <yloc>448</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>Count rows of empty then good</name>
+    <type>GroupBy</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <all_rows>N</all_rows>
+    <ignore_aggregate>N</ignore_aggregate>
+    <field_ignore/>
+    <directory>${java.io.tmpdir}</directory>
+    <prefix>grp</prefix>
+    <add_linenr>N</add_linenr>
+    <linenr_fieldname/>
+    <give_back_row>N</give_back_row>
+    <group>
+      </group>
+    <fields>
+      <field>
+        <aggregate>num_rows</aggregate>
+        <subject/>
+        <type>COUNT_ANY</type>
+        <valuefield/>
+      </field>
+    </fields>
+    <attributes/>
+    <GUI>
+      <xloc>384</xloc>
+      <yloc>448</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>Check empty then good</name>
+    <type>FilterRows</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <send_true_to>Empty then good OK</send_true_to>
+    <send_false_to>Empty then good wrong count - Abort</send_false_to>
+    <compare>
+      <condition>
+        <negated>N</negated>
+        <leftvalue>num_rows</leftvalue>
+        <function>=</function>
+        <rightvalue/>
+        <value>
+          <name>constant</name>
+          <type>Integer</type>
+          <text>3</text>
+          <length>-1</length>
+          <precision>0</precision>
+          <isnull>N</isnull>
+          <mask>####0;-####0</mask>
+        </value>
+      </condition>
+    </compare>
+    <attributes/>
+    <GUI>
+      <xloc>592</xloc>
+      <yloc>448</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>Empty then good OK</name>
+    <type>Dummy</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <attributes/>
+    <GUI>
+      <xloc>816</xloc>
+      <yloc>448</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>Empty then good wrong count - Abort</name>
+    <type>Abort</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <abort_option>ABORT_WITH_ERROR</abort_option>
+    <always_log_rows>Y</always_log_rows>
+    <message>Reading an ignored empty file followed by a good file did not 
return 3 rows</message>
+    <row_threshold>0</row_threshold>
+    <attributes/>
+    <GUI>
+      <xloc>592</xloc>
+      <yloc>560</yloc>
+    </GUI>
+  </transform>
+  <transform_error_handling>
+  </transform_error_handling>
+  <attributes/>
+</pipeline>
diff --git a/integration-tests/json/0014-json-input-fail-on-empty-file.hpl 
b/integration-tests/json/0014-json-input-fail-on-empty-file.hpl
new file mode 100644
index 0000000000..af2daea05a
--- /dev/null
+++ b/integration-tests/json/0014-json-input-fail-on-empty-file.hpl
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+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.
+
+-->
+<pipeline>
+  <info>
+    <name>0014-json-input-fail-on-empty-file</name>
+    <name_sync_with_filename>Y</name_sync_with_filename>
+    <description>Issue #8071: without "ignore empty file" a zero byte file 
must still be an error</description>
+    <extended_description/>
+    <pipeline_version/>
+    <pipeline_type>Normal</pipeline_type>
+    <parameters>
+    </parameters>
+    <capture_transform_performance>N</capture_transform_performance>
+    
<transform_performance_capturing_delay>1000</transform_performance_capturing_delay>
+    
<transform_performance_capturing_size_limit>100</transform_performance_capturing_size_limit>
+    <created_user>-</created_user>
+    <created_date>2026/08/25 10:00:00.000</created_date>
+    <modified_user>-</modified_user>
+    <modified_date>2026/08/25 10:00:00.000</modified_date>
+  </info>
+  <notepads>
+  </notepads>
+  <order>
+    <hop>
+      <from>Read empty file without ignoring it</from>
+      <to>Rows</to>
+      <enabled>Y</enabled>
+    </hop>
+  </order>
+  <transform>
+    <name>Read empty file without ignoring it</name>
+    <type>JsonInput</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <include>N</include>
+    <include_field/>
+    <rownum>N</rownum>
+    <rownum_field/>
+    <limit>0</limit>
+    <valueField/>
+    <IsInFields>N</IsInFields>
+    <IsAFile>N</IsAFile>
+    <addresultfile>N</addresultfile>
+    <IsIgnoreEmptyFile>N</IsIgnoreEmptyFile>
+    <doNotFailIfNoFile>N</doNotFailIfNoFile>
+    <ignoreMissingPath>Y</ignoreMissingPath>
+    <readurl>N</readurl>
+    <removeSourceField>N</removeSourceField>
+    <defaultPathLeafToNull>Y</defaultPathLeafToNull>
+    <files>
+      <file>
+        <name>${java.io.tmpdir}/hop-it-0014-empty.json</name>
+        <filemask/>
+        <exclude_filemask/>
+        <file_required>N</file_required>
+        <include_subfolders>N</include_subfolders>
+      </file>
+    </files>
+    <fields>
+      <field>
+        <name>name</name>
+        <path>$.fighters[*].name</path>
+        <type>String</type>
+        <format/>
+        <currency/>
+        <decimal/>
+        <group/>
+        <length>-1</length>
+        <precision>-1</precision>
+        <trim_type>none</trim_type>
+        <repeat>N</repeat>
+      </field>
+    </fields>
+    <attributes/>
+    <GUI>
+      <xloc>160</xloc>
+      <yloc>96</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>Rows</name>
+    <type>Dummy</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <attributes/>
+    <GUI>
+      <xloc>384</xloc>
+      <yloc>96</yloc>
+    </GUI>
+  </transform>
+  <transform_error_handling>
+  </transform_error_handling>
+  <attributes/>
+</pipeline>
diff --git a/integration-tests/json/main-0013-json-input-ignore-empty-file.hwf 
b/integration-tests/json/main-0013-json-input-ignore-empty-file.hwf
new file mode 100644
index 0000000000..6eeed1bd70
--- /dev/null
+++ b/integration-tests/json/main-0013-json-input-ignore-empty-file.hwf
@@ -0,0 +1,149 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+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.
+
+-->
+<workflow>
+  <name>main-0013-json-input-ignore-empty-file</name>
+  <name_sync_with_filename>Y</name_sync_with_filename>
+  <description/>
+  <extended_description/>
+  <workflow_version/>
+  <created_user>-</created_user>
+  <created_date>2026/08/25 10:00:00.000</created_date>
+  <modified_user>-</modified_user>
+  <modified_date>2026/08/25 10:00:00.000</modified_date>
+  <parameters>
+    </parameters>
+  <actions>
+    <action>
+      <name>Start</name>
+      <description/>
+      <type>SPECIAL</type>
+      <attributes/>
+      <DayOfMonth>1</DayOfMonth>
+      <doNotWaitOnFirstExecution>N</doNotWaitOnFirstExecution>
+      <hour>12</hour>
+      <intervalMinutes>60</intervalMinutes>
+      <intervalSeconds>0</intervalSeconds>
+      <minutes>0</minutes>
+      <repeat>N</repeat>
+      <schedulerType>0</schedulerType>
+      <weekDay>1</weekDay>
+      <parallel>N</parallel>
+      <xloc>64</xloc>
+      <yloc>64</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>Delete leftover empty file</name>
+      <description/>
+      <type>DELETE_FILE</type>
+      <attributes/>
+      <filename>${java.io.tmpdir}/hop-it-0013-empty.json</filename>
+      <fail_if_file_not_exists>N</fail_if_file_not_exists>
+      <parallel>N</parallel>
+      <xloc>224</xloc>
+      <yloc>64</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>Create empty file</name>
+      <description/>
+      <type>CREATE_FILE</type>
+      <attributes/>
+      <filename>${java.io.tmpdir}/hop-it-0013-empty.json</filename>
+      <fail_if_file_exists>N</fail_if_file_exists>
+      <add_filename_result>N</add_filename_result>
+      <parallel>N</parallel>
+      <xloc>416</xloc>
+      <yloc>64</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      
<filename>${PROJECT_HOME}/0013-json-input-ignore-empty-file.hpl</filename>
+      <params_from_previous>N</params_from_previous>
+      <exec_per_row>N</exec_per_row>
+      <clear_rows>N</clear_rows>
+      <clear_files>N</clear_files>
+      <create_parent_folder>N</create_parent_folder>
+      <set_logfile>N</set_logfile>
+      <set_append_logfile>N</set_append_logfile>
+      <add_date>N</add_date>
+      <add_time>N</add_time>
+      <loglevel>Basic</loglevel>
+      <wait_until_finished>Y</wait_until_finished>
+      <parameters>
+        <pass_all_parameters>Y</pass_all_parameters>
+      </parameters>
+      <run_configuration>local</run_configuration>
+      <name>0013-json-input-ignore-empty-file.hpl</name>
+      <description/>
+      <type>PIPELINE</type>
+      <attributes/>
+      <xloc>608</xloc>
+      <yloc>64</yloc>
+      <parallel>N</parallel>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>Delete empty file</name>
+      <description/>
+      <type>DELETE_FILE</type>
+      <attributes/>
+      <filename>${java.io.tmpdir}/hop-it-0013-empty.json</filename>
+      <fail_if_file_not_exists>N</fail_if_file_not_exists>
+      <parallel>N</parallel>
+      <xloc>816</xloc>
+      <yloc>64</yloc>
+      <attributes_hac/>
+    </action>
+  </actions>
+  <hops>
+    <hop>
+      <from>Start</from>
+      <to>Delete leftover empty file</to>
+      <enabled>Y</enabled>
+      <evaluation>Y</evaluation>
+      <unconditional>Y</unconditional>
+    </hop>
+    <hop>
+      <from>Delete leftover empty file</from>
+      <to>Create empty file</to>
+      <enabled>Y</enabled>
+      <evaluation>Y</evaluation>
+      <unconditional>N</unconditional>
+    </hop>
+    <hop>
+      <from>Create empty file</from>
+      <to>0013-json-input-ignore-empty-file.hpl</to>
+      <enabled>Y</enabled>
+      <evaluation>Y</evaluation>
+      <unconditional>N</unconditional>
+    </hop>
+    <hop>
+      <from>0013-json-input-ignore-empty-file.hpl</from>
+      <to>Delete empty file</to>
+      <enabled>Y</enabled>
+      <evaluation>Y</evaluation>
+      <unconditional>N</unconditional>
+    </hop>
+  </hops>
+  <notepads>
+  </notepads>
+  <attributes/>
+</workflow>
diff --git a/integration-tests/json/main-0014-json-input-fail-on-empty-file.hwf 
b/integration-tests/json/main-0014-json-input-fail-on-empty-file.hwf
new file mode 100644
index 0000000000..553647f77e
--- /dev/null
+++ b/integration-tests/json/main-0014-json-input-fail-on-empty-file.hwf
@@ -0,0 +1,168 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+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.
+
+-->
+<workflow>
+  <name>main-0014-json-input-fail-on-empty-file</name>
+  <name_sync_with_filename>Y</name_sync_with_filename>
+  <description>The pipeline is expected to fail: reading a zero byte file 
without "ignore empty file" is an error</description>
+  <extended_description/>
+  <workflow_version/>
+  <created_user>-</created_user>
+  <created_date>2026/08/25 10:00:00.000</created_date>
+  <modified_user>-</modified_user>
+  <modified_date>2026/08/25 10:00:00.000</modified_date>
+  <parameters>
+    </parameters>
+  <actions>
+    <action>
+      <name>Start</name>
+      <description/>
+      <type>SPECIAL</type>
+      <attributes/>
+      <DayOfMonth>1</DayOfMonth>
+      <doNotWaitOnFirstExecution>N</doNotWaitOnFirstExecution>
+      <hour>12</hour>
+      <intervalMinutes>60</intervalMinutes>
+      <intervalSeconds>0</intervalSeconds>
+      <minutes>0</minutes>
+      <repeat>N</repeat>
+      <schedulerType>0</schedulerType>
+      <weekDay>1</weekDay>
+      <parallel>N</parallel>
+      <xloc>64</xloc>
+      <yloc>64</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>Delete leftover empty file</name>
+      <description/>
+      <type>DELETE_FILE</type>
+      <attributes/>
+      <filename>${java.io.tmpdir}/hop-it-0014-empty.json</filename>
+      <fail_if_file_not_exists>N</fail_if_file_not_exists>
+      <parallel>N</parallel>
+      <xloc>224</xloc>
+      <yloc>64</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>Create empty file</name>
+      <description/>
+      <type>CREATE_FILE</type>
+      <attributes/>
+      <filename>${java.io.tmpdir}/hop-it-0014-empty.json</filename>
+      <fail_if_file_exists>N</fail_if_file_exists>
+      <add_filename_result>N</add_filename_result>
+      <parallel>N</parallel>
+      <xloc>416</xloc>
+      <yloc>64</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      
<filename>${PROJECT_HOME}/0014-json-input-fail-on-empty-file.hpl</filename>
+      <params_from_previous>N</params_from_previous>
+      <exec_per_row>N</exec_per_row>
+      <clear_rows>N</clear_rows>
+      <clear_files>N</clear_files>
+      <create_parent_folder>N</create_parent_folder>
+      <set_logfile>N</set_logfile>
+      <set_append_logfile>N</set_append_logfile>
+      <add_date>N</add_date>
+      <add_time>N</add_time>
+      <loglevel>Basic</loglevel>
+      <wait_until_finished>Y</wait_until_finished>
+      <parameters>
+        <pass_all_parameters>Y</pass_all_parameters>
+      </parameters>
+      <run_configuration>local</run_configuration>
+      <name>0014-json-input-fail-on-empty-file.hpl</name>
+      <description>Expected to fail</description>
+      <type>PIPELINE</type>
+      <attributes/>
+      <xloc>608</xloc>
+      <yloc>64</yloc>
+      <parallel>N</parallel>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>Empty file did not fail - Abort</name>
+      <description/>
+      <type>ABORT</type>
+      <attributes/>
+      <message>Reading a zero byte file without "ignore empty file" did not 
report an error</message>
+      <loglevel>Basic</loglevel>
+      <parallel>N</parallel>
+      <xloc>608</xloc>
+      <yloc>208</yloc>
+      <attributes_hac/>
+    </action>
+    <action>
+      <name>Delete empty file</name>
+      <description/>
+      <type>DELETE_FILE</type>
+      <attributes/>
+      <filename>${java.io.tmpdir}/hop-it-0014-empty.json</filename>
+      <fail_if_file_not_exists>N</fail_if_file_not_exists>
+      <parallel>N</parallel>
+      <xloc>832</xloc>
+      <yloc>64</yloc>
+      <attributes_hac/>
+    </action>
+  </actions>
+  <hops>
+    <hop>
+      <from>Start</from>
+      <to>Delete leftover empty file</to>
+      <enabled>Y</enabled>
+      <evaluation>Y</evaluation>
+      <unconditional>Y</unconditional>
+    </hop>
+    <hop>
+      <from>Delete leftover empty file</from>
+      <to>Create empty file</to>
+      <enabled>Y</enabled>
+      <evaluation>Y</evaluation>
+      <unconditional>N</unconditional>
+    </hop>
+    <hop>
+      <from>Create empty file</from>
+      <to>0014-json-input-fail-on-empty-file.hpl</to>
+      <enabled>Y</enabled>
+      <evaluation>Y</evaluation>
+      <unconditional>N</unconditional>
+    </hop>
+    <hop>
+      <from>0014-json-input-fail-on-empty-file.hpl</from>
+      <to>Empty file did not fail - Abort</to>
+      <enabled>Y</enabled>
+      <evaluation>Y</evaluation>
+      <unconditional>N</unconditional>
+    </hop>
+    <hop>
+      <from>0014-json-input-fail-on-empty-file.hpl</from>
+      <to>Delete empty file</to>
+      <enabled>Y</enabled>
+      <evaluation>N</evaluation>
+      <unconditional>N</unconditional>
+    </hop>
+  </hops>
+  <notepads>
+  </notepads>
+  <attributes/>
+</workflow>
diff --git 
a/integration-tests/json/resources/json-input-ignore-empty-file-it.json 
b/integration-tests/json/resources/json-input-ignore-empty-file-it.json
new file mode 100644
index 0000000000..3b30d95998
--- /dev/null
+++ b/integration-tests/json/resources/json-input-ignore-empty-file-it.json
@@ -0,0 +1,7 @@
+{
+  "fighters": [
+    { "name": "Goku", "power": 9500 },
+    { "name": "Vegeta", "power": 9000 },
+    { "name": "Gohan", "power": 8500 }
+  ]
+}
diff --git 
a/plugins/transforms/json/src/main/java/org/apache/hop/pipeline/transforms/jsoninput/JsonInput.java
 
b/plugins/transforms/json/src/main/java/org/apache/hop/pipeline/transforms/jsoninput/JsonInput.java
index 59ecb79ec1..178aab2ef3 100644
--- 
a/plugins/transforms/json/src/main/java/org/apache/hop/pipeline/transforms/jsoninput/JsonInput.java
+++ 
b/plugins/transforms/json/src/main/java/org/apache/hop/pipeline/transforms/jsoninput/JsonInput.java
@@ -29,6 +29,7 @@ import java.util.List;
 import org.apache.commons.lang3.NotImplementedException;
 import org.apache.commons.vfs2.FileObject;
 import org.apache.commons.vfs2.FileSystemException;
+import org.apache.hop.core.IRowSet;
 import org.apache.hop.core.QueueRowSet;
 import org.apache.hop.core.ResultFile;
 import org.apache.hop.core.exception.HopException;
@@ -196,7 +197,12 @@ public class JsonInput extends 
BaseFileInputTransform<JsonInputMeta, JsonInputDa
     data.inputRowMeta = getInputRowMeta();
     if (!meta.isInFields() && data.inputRowMeta == null) {
       data.outputRowMeta = new RowMeta();
-      if (!meta.isDoNotFailIfNoFile() && (data.files == null || 
data.files.nrOfFiles() == 0)) {
+      if (data.files == null) {
+        // Resolve the file list here: the reader below only does it while 
iterating, which is too
+        // late to tell whether there are any files at all.
+        data.files = meta.getFileInputList(this);
+      }
+      if (!meta.isDoNotFailIfNoFile() && data.files.nrOfFiles() == 0) {
         String errMsg = BaseMessages.getString(PKG, "JsonInput.Log.NoFiles");
         logError(errMsg);
         inputError(errMsg);
@@ -319,7 +325,9 @@ public class JsonInput extends 
BaseFileInputTransform<JsonInputMeta, JsonInputDa
       if (meta.isIgnoringEmptyFile()) {
         int first = pb.read();
         if (first < 0) {
-          data.readerRowSet = data.jsonReader.emptyFieldRowSet();
+          // Empty input while ignoring empty files: a file is skipped without 
any output row, a
+          // field value keeps the incoming row with null values.
+          data.readerRowSet = isReadingFiles() ? emptyRowSet() : 
data.jsonReader.emptyFieldRowSet();
           return;
         }
         pb.unread(first);
@@ -335,6 +343,18 @@ public class JsonInput extends 
BaseFileInputTransform<JsonInputMeta, JsonInputDa
     }
   }
 
+  /** Are we reading the JSON from files (file list or file names in a field)? 
*/
+  private boolean isReadingFiles() {
+    return !meta.isInFields() || meta.getIsAFile();
+  }
+
+  /** A finished row set without any rows: nothing gets written for this 
input. */
+  private IRowSet emptyRowSet() {
+    IRowSet rowSet = new QueueRowSet();
+    rowSet.setDone();
+    return rowSet;
+  }
+
   private void parseNextJsonToRowSet(JsonNode node) throws HopException {
     try {
       data.readerRowSet = data.jsonReader.parseJsonNodeValue(node);
@@ -478,6 +498,10 @@ public class JsonInput extends 
BaseFileInputTransform<JsonInputMeta, JsonInputDa
               }
               BaseTransform.closeQuietly(countingIn);
             }
+          } else if (data.skipEmptyFile) {
+            // The last file(s) in the list were empty and are ignored: emit 
nothing for them.
+            data.skipEmptyFile = false;
+            data.readerRowSet = emptyRowSet();
           } else {
             // Null stream (e.g. null field, iterator error path): do not 
parse "{}" when ignoring
             // empty input — that would still run JSONPath and fail if paths 
are missing.
diff --git 
a/plugins/transforms/json/src/main/java/org/apache/hop/pipeline/transforms/jsoninput/reader/InputsReader.java
 
b/plugins/transforms/json/src/main/java/org/apache/hop/pipeline/transforms/jsoninput/reader/InputsReader.java
index a29b9f2e18..1ed99f06eb 100644
--- 
a/plugins/transforms/json/src/main/java/org/apache/hop/pipeline/transforms/jsoninput/reader/InputsReader.java
+++ 
b/plugins/transforms/json/src/main/java/org/apache/hop/pipeline/transforms/jsoninput/reader/InputsReader.java
@@ -157,13 +157,18 @@ public class InputsReader implements 
Iterable<InputStream> {
           }
         }
         try {
+          JsonInputData jsonData = data instanceof JsonInputData jd ? jd : 
null;
+          if (jsonData != null) {
+            jsonData.skipEmptyFile = false;
+          }
           data.file = inner.next();
           data.currentFileIndex++;
           if (transform.onNewFile(data.file)) {
             return HopVfs.getInputStream(data.file);
           }
-          if (data instanceof JsonInputData jsonData && 
jsonData.skipEmptyFile) {
-            jsonData.skipEmptyFile = false;
+          if (jsonData != null && jsonData.skipEmptyFile) {
+            // Empty file, ignored: move on to the next one. When there is 
none left we return null
+            // with the flag still set, telling the transform not to write a 
row for it.
             continue;
           }
           return null;
diff --git 
a/plugins/transforms/json/src/test/java/org/apache/hop/pipeline/transforms/jsoninput/JsonInputTest.java
 
b/plugins/transforms/json/src/test/java/org/apache/hop/pipeline/transforms/jsoninput/JsonInputTest.java
index 1ce8598970..0ba02a6eca 100644
--- 
a/plugins/transforms/json/src/test/java/org/apache/hop/pipeline/transforms/jsoninput/JsonInputTest.java
+++ 
b/plugins/transforms/json/src/test/java/org/apache/hop/pipeline/transforms/jsoninput/JsonInputTest.java
@@ -1176,6 +1176,100 @@ class JsonInputTest {
     }
   }
 
+  @Test
+  void testZeroSizeFileIgnoredEmitsNoRow() throws Exception {
+    ByteArrayOutputStream err = new ByteArrayOutputStream();
+    helper.redirectLog(err, LogLevel.ERROR);
+    try (FileObject empty = HopVfs.getFileObject(BASE_RAM_DIR + "empty.json")) 
{
+      empty.createFile();
+
+      JsonInputField price = new JsonInputField();
+      price.setName("price");
+      price.setType(IValueMeta.TYPE_NUMBER);
+      price.setPath("$..book[*].price");
+
+      JsonInputMeta meta = createFileListMeta(List.of(empty));
+      meta.getInputFields().add(price);
+      meta.setIgnoringEmptyFile(true);
+
+      JsonInput jsonInput = createJsonInput(meta);
+      processRows(jsonInput, 3);
+      disposeJsonInput(jsonInput);
+
+      assertEquals(0, jsonInput.getErrors(), err.toString());
+      assertEquals(0, jsonInput.getLinesWritten(), "rows written");
+    } finally {
+      deleteFiles();
+    }
+  }
+
+  @Test
+  void testZeroSizeFileNotIgnoredIsAnError() throws Exception {
+    ByteArrayOutputStream err = new ByteArrayOutputStream();
+    helper.redirectLog(err, LogLevel.ERROR);
+    try (FileObject empty = HopVfs.getFileObject(BASE_RAM_DIR + "empty.json");
+        LocaleChange enUs = new LocaleChange(Locale.US)) {
+      empty.createFile();
+
+      JsonInputField price = new JsonInputField();
+      price.setName("price");
+      price.setType(IValueMeta.TYPE_NUMBER);
+      price.setPath("$..book[*].price");
+
+      JsonInputMeta meta = createFileListMeta(List.of(empty));
+      meta.getInputFields().add(price);
+      meta.setIgnoringEmptyFile(false);
+      meta.setIgnoringMissingPath(true);
+
+      JsonInput jsonInput = createJsonInput(meta);
+      processRows(jsonInput, 3);
+      disposeJsonInput(jsonInput);
+
+      String logMsgs = err.toString();
+      assertTrue(logMsgs.contains("is empty!"), logMsgs);
+      assertEquals(1, jsonInput.getErrors(), "errors");
+    } finally {
+      deleteFiles();
+    }
+  }
+
+  @Test
+  void testZeroSizeLastFileIgnoredEmitsNoExtraRow() throws Exception {
+    ByteArrayOutputStream err = new ByteArrayOutputStream();
+    helper.redirectLog(err, LogLevel.ERROR);
+    try (FileObject good = HopVfs.getFileObject(BASE_RAM_DIR + "good.json");
+        FileObject empty = HopVfs.getFileObject(BASE_RAM_DIR + "empty.json")) {
+      try (OutputStream os = good.getContent().getOutputStream()) {
+        IOUtils.write(getBasicTestJson(), os, StandardCharsets.UTF_8);
+      }
+      empty.createFile();
+
+      JsonInputField price = new JsonInputField();
+      price.setName("price");
+      price.setType(IValueMeta.TYPE_NUMBER);
+      price.setPath("$..book[*].price");
+
+      JsonInputMeta meta = createFileListMeta(List.of(good, empty));
+      meta.getInputFields().add(price);
+      meta.setIgnoringEmptyFile(true);
+
+      JsonInput jsonInput = createJsonInput(meta);
+      jsonInput.addRowListener(
+          new RowComparatorListener(
+              new Object[] {8.95d},
+              new Object[] {12.99d},
+              new Object[] {8.99d},
+              new Object[] {22.99d}));
+      processRows(jsonInput, 8);
+      disposeJsonInput(jsonInput);
+
+      assertEquals(0, jsonInput.getErrors(), err.toString());
+      assertEquals(4, jsonInput.getLinesWritten(), "rows written");
+    } finally {
+      deleteFiles();
+    }
+  }
+
   @Test
   void testBracketEscape() throws Exception {
     String input = "{\"a\":1,\"b(1)\":2}";

Reply via email to