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

github-merge-queue[bot] pushed a commit to branch 
gh-readonly-queue/dev/pr-12768-f72a75f1aa120cae0cb7b08d135297950e08132f
in repository https://gitbox.apache.org/repos/asf/seatunnel.git

commit 14bcba582d256c50609a7e4c3fb30ab078d1567c
Author: JeremyXin <[email protected]>
AuthorDate: Sun Oct 18 14:02:38 2026 +0000

    [Fix][Connector-File] Fix Excel column mapping and read_columns projection 
(#12768)
    
    Co-authored-by: JeremyXin <--golbal>
---
 docs/en/connectors/source/LocalFile.md             |   9 +-
 docs/zh/connectors/source/LocalFile.md             |   9 +-
 .../file/config/FileBaseSourceOptions.java         |   8 ++
 .../seatunnel/file/excel/ExcelReaderListener.java  |  65 ++++++++-
 .../file/source/reader/ExcelReadStrategy.java      |  70 +++++++--
 .../file/reader/ExcelReadStrategyTest.java         | 157 +++++++++++++++++++++
 .../file/bos/source/BosFileSourceFactory.java      |   1 +
 .../file/cos/source/CosFileSourceFactory.java      |   1 +
 .../file/ftp/source/FtpFileSourceFactory.java      |   1 +
 .../file/gcs/source/GcsFileSourceFactory.java      |   1 +
 .../file/hdfs/source/HdfsFileSourceFactory.java    |   1 +
 .../oss/jindo/source/OssFileSourceFactory.java     |   1 +
 .../file/local/source/LocalFileSourceFactory.java  |   1 +
 .../seatunnel/file/local/LocalFileFactoryTest.java |   4 +
 .../file/obs/source/ObsFileSourceFactory.java      |   1 +
 .../file/oss/source/OssFileSourceFactory.java      |   1 +
 .../file/s3/source/S3FileSourceFactory.java        |   1 +
 .../file/sftp/source/SftpFileSourceFactory.java    |   1 +
 .../file/smb/source/SmbFileSourceFactory.java      |   1 +
 .../e2e/connector/file/local/LocalFileIT.java      |  31 ++++
 ...xcel_header_projection_easyexcel_to_assert.conf |  61 ++++++++
 ...ocal_excel_header_projection_poi_to_assert.conf |  61 ++++++++
 22 files changed, 468 insertions(+), 19 deletions(-)

diff --git a/docs/en/connectors/source/LocalFile.md 
b/docs/en/connectors/source/LocalFile.md
index 413101e451..31885568e9 100644
--- a/docs/en/connectors/source/LocalFile.md
+++ b/docs/en/connectors/source/LocalFile.md
@@ -67,6 +67,7 @@ If you use SeaTunnel Engine, It automatically integrated the 
hadoop jar when you
 | schema                     | config  | no       | -                          
          |
 | sheet_name                 | string  | no       | -                          
          |
 | excel_engine               | string  | no       | POI                        
          |
+| excel_use_header_line      | boolean | no       | false                      
          |
 | poi_excel_max_file_size    | long    | no       | 52428800                   
          |
 | xml_row_tag                | string  | no       | -                          
          |
 | xml_use_attr_format        | boolean | no       | -                          
          |
@@ -314,7 +315,7 @@ default `HH:mm:ss`
 
 ### skip_header_row_number [long]
 
-Skip the first few lines, but only for the txt and csv.
+Skip the first few lines for text, CSV, and Excel.
 
 For example, set like following:
 
@@ -357,6 +358,12 @@ The default Excel reading engine is POI. POI keeps the 
historical read behavior,
 
 You can set `excel_engine = EasyExcel` to use streaming reads for large Excel 
files.
 
+### excel_use_header_line [boolean]
+
+Only used when `file_format_type` is `excel`. The default is `false`, so Excel 
columns are read by position even when `skip_header_row_number` is set.
+
+Set `excel_use_header_line = true` to use the first row as the header and map 
schema fields by name. This skips the first row; when both options are set, 
`excel_use_header_line` takes precedence over `skip_header_row_number`. Fields 
missing from the header produce null values and a warning.
+
 ### poi_excel_max_file_size [long]
 
 Only used when `file_format` is excel and `excel_engine` is POI.
diff --git a/docs/zh/connectors/source/LocalFile.md 
b/docs/zh/connectors/source/LocalFile.md
index 396274b888..3519e80980 100644
--- a/docs/zh/connectors/source/LocalFile.md
+++ b/docs/zh/connectors/source/LocalFile.md
@@ -67,6 +67,7 @@ import ChangeLog from '../changelog/connector-file-local.md';
 | schema                     | config  | 否    | -                   |
 | sheet_name                 | string  | 否    | -                   |
 | excel_engine               | string  | 否    | POI                 |
+| excel_use_header_line      | boolean | 否    | false               |
 | poi_excel_max_file_size    | long    | 否    | 52428800            |
 | xml_row_tag                | string  | 否    | -                   |
 | xml_use_attr_format        | boolean | 否    | -                   |
@@ -314,7 +315,7 @@ PDF 特有的解析行为如下:
 
 ### skip_header_row_number [long]
 
-跳过前几行,但仅适用于 txt 和 csv。
+跳过 text、CSV 或 Excel 文件的前几行。
 
 例如,设置如下:
 
@@ -357,6 +358,12 @@ PDF 特有的解析行为如下:
 
 如果需要读取大 Excel 文件,可以设置 `excel_engine = EasyExcel` 使用流式读取。
 
+### excel_use_header_line [boolean]
+
+仅用于 `file_format_type` 为 `excel` 的源。默认值为 `false`,因此即使设置了 
`skip_header_row_number`,Excel 仍按列位置读取。
+
+设置 `excel_use_header_line = true` 后,第一行作为表头,按列名映射到 schema 字段,并跳过第一行。如果同时设置了 
`skip_header_row_number`,则以 `excel_use_header_line` 为准。表头中缺少的字段将产生 null 值并记录警告。
+
 ### poi_excel_max_file_size [long]
 
 仅在 `file_format` 为 excel 且 `excel_engine` 为 POI 时使用。
diff --git 
a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/config/FileBaseSourceOptions.java
 
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/config/FileBaseSourceOptions.java
index edffd618d8..85e134bf9b 100644
--- 
a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/config/FileBaseSourceOptions.java
+++ 
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/config/FileBaseSourceOptions.java
@@ -97,6 +97,14 @@ public class FileBaseSourceOptions extends FileBaseOptions {
                     .defaultValue(0L)
                     .withDescription("The number of rows to skip");
 
+    public static final Option<Boolean> EXCEL_USE_HEADER_LINE =
+            Options.key("excel_use_header_line")
+                    .booleanType()
+                    .defaultValue(false)
+                    .withDescription(
+                            "Use the first Excel row as column headers for 
name-based mapping. "
+                                    + "Only used when file_format_type is 
excel.");
+
     public static final Option<List<String>> READ_PARTITIONS =
             Options.key("read_partitions")
                     .listType()
diff --git 
a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/excel/ExcelReaderListener.java
 
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/excel/ExcelReaderListener.java
index 297e667b65..5cb2ab51b6 100644
--- 
a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/excel/ExcelReaderListener.java
+++ 
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/excel/ExcelReaderListener.java
@@ -19,6 +19,7 @@ package org.apache.seatunnel.connectors.seatunnel.file.excel;
 
 import org.apache.seatunnel.shade.com.fasterxml.jackson.databind.ObjectMapper;
 import org.apache.seatunnel.shade.com.typesafe.config.Config;
+import org.apache.seatunnel.shade.org.apache.commons.lang3.StringUtils;
 
 import org.apache.seatunnel.api.source.Collector;
 import org.apache.seatunnel.api.table.type.SeaTunnelDataType;
@@ -52,6 +53,10 @@ public class ExcelReaderListener extends 
AnalysisEventListener<Map<Integer, Obje
     protected SeaTunnelRowType seaTunnelRowType;
 
     private SeaTunnelDataType<?>[] fieldTypes;
+    private final int[] indexes;
+    private final boolean useHeaderLine;
+    // Maps output field indexes to Excel column indexes.
+    private final Map<Integer, Integer> headerColumns = new HashMap<>();
 
     private ExcelCellUtils excelCellUtils;
 
@@ -62,20 +67,59 @@ public class ExcelReaderListener extends 
AnalysisEventListener<Map<Integer, Obje
             Collector<SeaTunnelRow> output,
             ExcelCellUtils excelCellUtils,
             SeaTunnelRowType seaTunnelRowType) {
+        this(tableId, output, excelCellUtils, seaTunnelRowType, null, true);
+    }
+
+    public ExcelReaderListener(
+            String tableId,
+            Collector<SeaTunnelRow> output,
+            ExcelCellUtils excelCellUtils,
+            SeaTunnelRowType seaTunnelRowType,
+            int[] indexes) {
+        this(tableId, output, excelCellUtils, seaTunnelRowType, indexes, true);
+    }
+
+    public ExcelReaderListener(
+            String tableId,
+            Collector<SeaTunnelRow> output,
+            ExcelCellUtils excelCellUtils,
+            SeaTunnelRowType seaTunnelRowType,
+            int[] indexes,
+            boolean useHeaderLine) {
         this.tableId = tableId;
         this.output = output;
         this.excelCellUtils = excelCellUtils;
         this.seaTunnelRowType = seaTunnelRowType;
+        this.indexes = indexes;
+        this.useHeaderLine = useHeaderLine;
 
         fieldTypes = seaTunnelRowType.getFieldTypes();
     }
 
     @Override
     public void invokeHead(Map<Integer, ReadCellData<?>> headMap, 
AnalysisContext context) {
-        for (int i = 0; i < headMap.size(); i++) {
-            String header = headMap.get(i).getStringValue();
-            if (!"null".equals(header)) {
-                customHeaders.put(i, header);
+        headerColumns.clear();
+        customHeaders.clear();
+        for (Map.Entry<Integer, ReadCellData<?>> entry : headMap.entrySet()) {
+            String header = entry.getValue().getStringValue();
+            if (StringUtils.isNotEmpty(header) && !"null".equals(header)) {
+                customHeaders.put(entry.getKey(), header);
+                // Schema fields may appear in a different order than the 
Excel columns.
+                if (useHeaderLine) {
+                    int fieldIndex = seaTunnelRowType.indexOf(header, false);
+                    if (fieldIndex >= 0) {
+                        headerColumns.put(fieldIndex, entry.getKey());
+                    }
+                }
+            }
+        }
+        if (useHeaderLine) {
+            for (int i = 0; i < fieldTypes.length; i++) {
+                if (!headerColumns.containsKey(i)) {
+                    log.warn(
+                            "Excel header does not contain schema field '{}'; 
values will be null",
+                            seaTunnelRowType.getFieldName(i));
+                }
             }
         }
     }
@@ -87,11 +131,18 @@ public class ExcelReaderListener extends 
AnalysisEventListener<Map<Integer, Obje
         Map<Integer, Cell> cellMap = context.readRowHolder().getCellMap();
         int i = 0;
         for (; i < fieldTypes.length; i++) {
-            if (cellMap.get(i) == null) {
+            Integer columnIndex;
+            if (!useHeaderLine) {
+                columnIndex = indexes == null ? i : indexes[i];
+            } else {
+                columnIndex = headerColumns.get(i);
+            }
+            Cell cell = columnIndex == null ? null : cellMap.get(columnIndex);
+            if (cell == null) {
                 seaTunnelRow.setField(i, null);
             } else {
-                Object cell = excelCellUtils.convert(data.get(i), 
fieldTypes[i], cellMap.get(i));
-                seaTunnelRow.setField(i, cell);
+                Object value = excelCellUtils.convert(data.get(columnIndex), 
fieldTypes[i], cell);
+                seaTunnelRow.setField(i, value);
             }
         }
         seaTunnelRow.setTableId(tableId);
diff --git 
a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/ExcelReadStrategy.java
 
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/ExcelReadStrategy.java
index d63143f278..697f9be30a 100644
--- 
a/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/ExcelReadStrategy.java
+++ 
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/source/reader/ExcelReadStrategy.java
@@ -17,6 +17,8 @@
 
 package org.apache.seatunnel.connectors.seatunnel.file.source.reader;
 
+import org.apache.seatunnel.shade.com.typesafe.config.Config;
+
 import org.apache.seatunnel.api.source.Collector;
 import org.apache.seatunnel.api.table.catalog.CatalogTable;
 import org.apache.seatunnel.api.table.type.SeaTunnelDataType;
@@ -42,6 +44,7 @@ import org.apache.poi.ss.usermodel.CellValue;
 import org.apache.poi.ss.usermodel.DataFormatter;
 import org.apache.poi.ss.usermodel.DateUtil;
 import org.apache.poi.ss.usermodel.FormulaEvaluator;
+import org.apache.poi.ss.usermodel.Row;
 import org.apache.poi.ss.usermodel.Sheet;
 import org.apache.poi.ss.usermodel.Workbook;
 import org.apache.poi.ss.util.NumberToTextConverter;
@@ -56,6 +59,7 @@ import lombok.extern.slf4j.Slf4j;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
 import java.util.stream.IntStream;
@@ -75,6 +79,8 @@ public class ExcelReadStrategy extends AbstractReadStrategy {
 
     private int cellCount;
 
+    private boolean useHeaderLine;
+
     @SneakyThrows
     @Override
     public void read(String path, String tableId, Collector<SeaTunnelRow> 
output) {
@@ -183,7 +189,13 @@ public class ExcelReadStrategy extends 
AbstractReadStrategy {
         ExcelReaderBuilder read =
                 EasyExcel.read(
                         inputStream,
-                        new ExcelReaderListener(tableId, output, 
excelCellUtils, seaTunnelRowType));
+                        new ExcelReaderListener(
+                                tableId,
+                                output,
+                                excelCellUtils,
+                                seaTunnelRowType,
+                                indexes,
+                                useHeaderLine));
         if (pluginConfig.hasPath(FileBaseSourceOptions.SHEET_NAME.key())) {
             
read.sheet(pluginConfig.getString(FileBaseSourceOptions.SHEET_NAME.key()))
                     .headRowNumber((int) skipHeaderNumber)
@@ -261,21 +273,46 @@ public class ExcelReadStrategy extends 
AbstractReadStrategy {
                     CommonErrorCodeDeprecated.UNSUPPORTED_OPERATION,
                     "Skip the number of rows exceeds the maximum or minimum 
limit of Sheet");
         }
+        int[] cellIndexes = indexes == null ? IntStream.range(0, 
cellCount).toArray() : indexes;
+        // Maps output field indexes to Excel column indexes.
+        // Header mapping is opt-in; skipping rows alone keeps positional 
reads.
+        Map<Integer, Integer> headerColumns = new HashMap<>();
+        if (useHeaderLine) {
+            Row headerRow = sheet.getRow(startRow - 1);
+            if (headerRow != null) {
+                for (Cell headerCell : headerRow) {
+                    String header = formatter.formatCellValue(headerCell, 
formulaEvaluator);
+                    int fieldIndex = seaTunnelRowType.indexOf(header, false);
+                    if (fieldIndex >= 0) {
+                        headerColumns.put(fieldIndex, 
headerCell.getColumnIndex());
+                    }
+                }
+            }
+            for (int i = 0; i < fieldTypes.length; i++) {
+                if (!headerColumns.containsKey(i)) {
+                    log.warn(
+                            "Excel file {} header does not contain schema 
field '{}'; values will be null",
+                            currentFileName,
+                            seaTunnelRowType.getFieldName(i));
+                }
+            }
+        }
         IntStream.range(startRow, lastRowNum + 1)
                 .mapToObj(sheet::getRow)
                 .filter(Objects::nonNull)
                 .forEach(
                         rowData -> {
-                            int[] cellIndexes =
-                                    indexes == null
-                                            ? IntStream.range(0, 
cellCount).toArray()
-                                            : indexes;
-                            int z = 0;
                             SeaTunnelRow seaTunnelRow = new 
SeaTunnelRow(cellCount);
-                            for (int j : cellIndexes) {
-                                Cell cell = rowData.getCell(j);
+                            for (int i = 0; i < fieldTypes.length; i++) {
+                                Integer columnIndex;
+                                columnIndex =
+                                        useHeaderLine
+                                                ? headerColumns.get(i)
+                                                : 
Integer.valueOf(cellIndexes[i]);
+                                Cell cell =
+                                        columnIndex == null ? null : 
rowData.getCell(columnIndex);
                                 seaTunnelRow.setField(
-                                        z++,
+                                        i,
                                         cell == null
                                                 ? null
                                                 : excelCellUtils.convert(
@@ -284,7 +321,7 @@ public class ExcelReadStrategy extends AbstractReadStrategy 
{
                                                                 cell,
                                                                 
formulaEvaluator,
                                                                 formatter),
-                                                        fieldTypes[z - 1],
+                                                        fieldTypes[i],
                                                         null));
                             }
                             if (isMergePartition) {
@@ -383,6 +420,19 @@ public class ExcelReadStrategy extends 
AbstractReadStrategy {
                 "User must defined schema for json file type");
     }
 
+    /**
+     * Loads the Excel header option. When enabled, the first row is used for 
column-name mapping
+     * and takes precedence over {@code skip_header_row_number}.
+     */
+    @Override
+    public void setPluginConfig(Config pluginConfig) {
+        super.setPluginConfig(pluginConfig);
+        useHeaderLine = 
readonlyConfig.get(FileBaseSourceOptions.EXCEL_USE_HEADER_LINE);
+        if (useHeaderLine) {
+            skipHeaderNumber = 1L;
+        }
+    }
+
     private Object getCellValue(
             CellType cellType,
             Cell cell,
diff --git 
a/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/reader/ExcelReadStrategyTest.java
 
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/reader/ExcelReadStrategyTest.java
index 3186f3421d..166bda2ab2 100644
--- 
a/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/reader/ExcelReadStrategyTest.java
+++ 
b/seatunnel-connectors-v2/connector-file/connector-file-base/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/reader/ExcelReadStrategyTest.java
@@ -243,6 +243,163 @@ public class ExcelReadStrategyTest {
         testLargeExcelRead("/excel/e2e.xlsx", "/excel/e2exls.conf", 5);
     }
 
+    @Test
+    public void testEasyExcelReadColumnsUsesSourceColumnIndexes()
+            throws IOException, URISyntaxException {
+        URL excelFile =
+                
ExcelReadStrategyTest.class.getResource("/excel/test_read_excel_date_string.xlsx");
+        URL conf =
+                
ExcelReadStrategyTest.class.getResource("/excel/test_read_excel_data_string.conf");
+        Assertions.assertNotNull(excelFile);
+        Assertions.assertNotNull(conf);
+        Config pluginConfig =
+                ConfigFactory.parseString("read_columns = [\"c_string\", 
\"c_int\"]")
+                        .withFallback(ConfigFactory.parseFile(new 
File(conf.toURI())));
+        ExcelReadStrategy readStrategy = new ExcelReadStrategy();
+        readStrategy.setPluginConfig(pluginConfig);
+        readStrategy.init(new LocalConf(FS_DEFAULT_NAME_DEFAULT));
+        
readStrategy.setCatalogTable(CatalogTableUtil.buildWithConfig(pluginConfig));
+
+        TestCollector collector = new TestCollector();
+        readStrategy.read(Paths.get(excelFile.toURI()).toString(), "", 
collector);
+
+        Assertions.assertEquals(1, collector.getRows().size());
+        SeaTunnelRow row = collector.getRows().get(0);
+        Assertions.assertEquals(2, row.getArity());
+        Assertions.assertEquals("Cosmos", row.getField(0));
+        Assertions.assertEquals(333, row.getField(1));
+    }
+
+    @Test
+    public void testReadColumnsMatchesExcelHeadersWithProjectedSchema()
+            throws IOException, URISyntaxException {
+        URL excelFile =
+                
ExcelReadStrategyTest.class.getResource("/excel/test_read_excel_date_string.xlsx");
+        Assertions.assertNotNull(excelFile);
+        for (String engine : new String[] {"POI", "EasyExcel"}) {
+            for (String skipRows : new String[] {"", "skip_header_row_number = 
2\n"}) {
+                Config pluginConfig =
+                        ConfigFactory.parseString(
+                                "file_format_type = excel\n"
+                                        + "excel_engine = "
+                                        + engine
+                                        + "\n"
+                                        + "excel_use_header_line = true\n"
+                                        + skipRows
+                                        + "schema { columns = [{name = 
c_bytes, type = string}, "
+                                        + "{name = c_int, type = int}, "
+                                        + "{name = c_string, type = string}] 
}\n"
+                                        + "read_columns = [c_string, c_int]");
+                ExcelReadStrategy readStrategy = new ExcelReadStrategy();
+                readStrategy.setPluginConfig(pluginConfig);
+                readStrategy.init(new LocalConf(FS_DEFAULT_NAME_DEFAULT));
+                
readStrategy.setCatalogTable(CatalogTableUtil.buildWithConfig(pluginConfig));
+
+                TestCollector collector = new TestCollector();
+                readStrategy.read(Paths.get(excelFile.toURI()).toString(), "", 
collector);
+
+                Assertions.assertEquals(1, collector.getRows().size(), engine);
+                SeaTunnelRow row = collector.getRows().get(0);
+                Assertions.assertEquals("Cosmos", row.getField(0), engine);
+                Assertions.assertEquals(333, row.getField(1), engine);
+            }
+        }
+    }
+
+    @Test
+    public void testExcelHeaderWithoutMatchingSchemaKeepsPositionalReading()
+            throws IOException, URISyntaxException {
+        URL excelFile =
+                
ExcelReadStrategyTest.class.getResource("/excel/test_read_excel_date_string.xlsx");
+        Assertions.assertNotNull(excelFile);
+        for (String engine : new String[] {"POI", "EasyExcel"}) {
+            Config pluginConfig =
+                    ConfigFactory.parseString(
+                            "file_format_type = excel\n"
+                                    + "excel_engine = "
+                                    + engine
+                                    + "\n"
+                                    + "skip_header_row_number = 1\n"
+                                    + "schema { columns = [{name = first, type 
= string}, "
+                                    + "{name = second, type = string}] }");
+            ExcelReadStrategy readStrategy = new ExcelReadStrategy();
+            readStrategy.setPluginConfig(pluginConfig);
+            readStrategy.init(new LocalConf(FS_DEFAULT_NAME_DEFAULT));
+            
readStrategy.setCatalogTable(CatalogTableUtil.buildWithConfig(pluginConfig));
+
+            TestCollector collector = new TestCollector();
+            readStrategy.read(Paths.get(excelFile.toURI()).toString(), "", 
collector);
+
+            Assertions.assertEquals(1, collector.getRows().size(), engine);
+            SeaTunnelRow row = collector.getRows().get(0);
+            Assertions.assertEquals("1", row.getField(0), engine);
+            Assertions.assertEquals("22", row.getField(1), engine);
+        }
+    }
+
+    @Test
+    public void testExcelPartialHeaderMatchWithoutOptInKeepsPositionalReading()
+            throws IOException, URISyntaxException {
+        URL excelFile =
+                
ExcelReadStrategyTest.class.getResource("/excel/test_read_excel_date_string.xlsx");
+        Assertions.assertNotNull(excelFile);
+        for (String engine : new String[] {"POI", "EasyExcel"}) {
+            Config pluginConfig =
+                    ConfigFactory.parseString(
+                            "file_format_type = excel\n"
+                                    + "excel_engine = "
+                                    + engine
+                                    + "\n"
+                                    + "skip_header_row_number = 1\n"
+                                    + "schema { columns = [{name = c_bytes, 
type = string}, "
+                                    + "{name = second, type = string}] }");
+            ExcelReadStrategy readStrategy = new ExcelReadStrategy();
+            readStrategy.setPluginConfig(pluginConfig);
+            readStrategy.init(new LocalConf(FS_DEFAULT_NAME_DEFAULT));
+            
readStrategy.setCatalogTable(CatalogTableUtil.buildWithConfig(pluginConfig));
+
+            TestCollector collector = new TestCollector();
+            readStrategy.read(Paths.get(excelFile.toURI()).toString(), "", 
collector);
+
+            Assertions.assertEquals(1, collector.getRows().size(), engine);
+            SeaTunnelRow row = collector.getRows().get(0);
+            Assertions.assertEquals("1", row.getField(0), engine);
+            Assertions.assertEquals("22", row.getField(1), engine);
+        }
+    }
+
+    @Test
+    public void testExcelHeaderMissingProjectedFieldReturnsNull()
+            throws IOException, URISyntaxException {
+        URL excelFile =
+                
ExcelReadStrategyTest.class.getResource("/excel/test_read_excel_date_string.xlsx");
+        Assertions.assertNotNull(excelFile);
+        for (String engine : new String[] {"POI", "EasyExcel"}) {
+            Config pluginConfig =
+                    ConfigFactory.parseString(
+                            "file_format_type = excel\n"
+                                    + "excel_engine = "
+                                    + engine
+                                    + "\n"
+                                    + "excel_use_header_line = true\n"
+                                    + "schema { columns = [{name = c_string, 
type = string}, "
+                                    + "{name = missing_field, type = string}] 
}\n"
+                                    + "read_columns = [c_string, 
missing_field]");
+            ExcelReadStrategy readStrategy = new ExcelReadStrategy();
+            readStrategy.setPluginConfig(pluginConfig);
+            readStrategy.init(new LocalConf(FS_DEFAULT_NAME_DEFAULT));
+            
readStrategy.setCatalogTable(CatalogTableUtil.buildWithConfig(pluginConfig));
+
+            TestCollector collector = new TestCollector();
+            readStrategy.read(Paths.get(excelFile.toURI()).toString(), "", 
collector);
+
+            Assertions.assertEquals(1, collector.getRows().size(), engine);
+            SeaTunnelRow row = collector.getRows().get(0);
+            Assertions.assertEquals("Cosmos", row.getField(0), engine);
+            Assertions.assertNull(row.getField(1), engine);
+        }
+    }
+
     @Test
     public void testEasyExcelIgnoresPoiFileSizeLimit() throws IOException, 
URISyntaxException {
         URL excelFile = 
ExcelReadStrategyTest.class.getResource("/excel/e2e.xlsx");
diff --git 
a/seatunnel-connectors-v2/connector-file/connector-file-bos/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/bos/source/BosFileSourceFactory.java
 
b/seatunnel-connectors-v2/connector-file/connector-file-bos/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/bos/source/BosFileSourceFactory.java
index d3e90a1719..06c4bd2f62 100644
--- 
a/seatunnel-connectors-v2/connector-file/connector-file-bos/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/bos/source/BosFileSourceFactory.java
+++ 
b/seatunnel-connectors-v2/connector-file/connector-file-bos/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/bos/source/BosFileSourceFactory.java
@@ -94,6 +94,7 @@ public class BosFileSourceFactory implements 
TableSourceFactory {
                 .optional(
                         FileBaseSourceOptions.SHEET_NAME,
                         FileBaseSourceOptions.EXCEL_ENGINE,
+                        FileBaseSourceOptions.EXCEL_USE_HEADER_LINE,
                         FileBaseSourceOptions.POI_EXCEL_MAX_FILE_SIZE)
                 .conditional(
                         FileBaseSourceOptions.FILE_FORMAT_TYPE,
diff --git 
a/seatunnel-connectors-v2/connector-file/connector-file-cos/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/cos/source/CosFileSourceFactory.java
 
b/seatunnel-connectors-v2/connector-file/connector-file-cos/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/cos/source/CosFileSourceFactory.java
index fe6422e75c..e918631e03 100644
--- 
a/seatunnel-connectors-v2/connector-file/connector-file-cos/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/cos/source/CosFileSourceFactory.java
+++ 
b/seatunnel-connectors-v2/connector-file/connector-file-cos/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/cos/source/CosFileSourceFactory.java
@@ -94,6 +94,7 @@ public class CosFileSourceFactory implements 
TableSourceFactory {
                 .optional(
                         FileBaseSourceOptions.SHEET_NAME,
                         FileBaseSourceOptions.EXCEL_ENGINE,
+                        FileBaseSourceOptions.EXCEL_USE_HEADER_LINE,
                         FileBaseSourceOptions.POI_EXCEL_MAX_FILE_SIZE)
                 .conditional(
                         FileBaseSourceOptions.FILE_FORMAT_TYPE,
diff --git 
a/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/source/FtpFileSourceFactory.java
 
b/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/source/FtpFileSourceFactory.java
index e965ffca14..2b2c0c26b1 100644
--- 
a/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/source/FtpFileSourceFactory.java
+++ 
b/seatunnel-connectors-v2/connector-file/connector-file-ftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/ftp/source/FtpFileSourceFactory.java
@@ -104,6 +104,7 @@ public class FtpFileSourceFactory implements 
TableSourceFactory {
                 .optional(
                         FileBaseSourceOptions.SHEET_NAME,
                         FileBaseSourceOptions.EXCEL_ENGINE,
+                        FileBaseSourceOptions.EXCEL_USE_HEADER_LINE,
                         FileBaseSourceOptions.POI_EXCEL_MAX_FILE_SIZE)
                 .conditional(
                         FileBaseSourceOptions.FILE_FORMAT_TYPE,
diff --git 
a/seatunnel-connectors-v2/connector-file/connector-file-gcs/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/gcs/source/GcsFileSourceFactory.java
 
b/seatunnel-connectors-v2/connector-file/connector-file-gcs/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/gcs/source/GcsFileSourceFactory.java
index 945cb2c03f..ad9ff53959 100644
--- 
a/seatunnel-connectors-v2/connector-file/connector-file-gcs/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/gcs/source/GcsFileSourceFactory.java
+++ 
b/seatunnel-connectors-v2/connector-file/connector-file-gcs/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/gcs/source/GcsFileSourceFactory.java
@@ -117,6 +117,7 @@ public class GcsFileSourceFactory implements 
TableSourceFactory {
                 .optional(
                         FileBaseSourceOptions.SHEET_NAME,
                         FileBaseSourceOptions.EXCEL_ENGINE,
+                        FileBaseSourceOptions.EXCEL_USE_HEADER_LINE,
                         FileBaseSourceOptions.POI_EXCEL_MAX_FILE_SIZE)
                 .conditional(
                         FileBaseSourceOptions.FILE_FORMAT_TYPE,
diff --git 
a/seatunnel-connectors-v2/connector-file/connector-file-hadoop/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/hdfs/source/HdfsFileSourceFactory.java
 
b/seatunnel-connectors-v2/connector-file/connector-file-hadoop/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/hdfs/source/HdfsFileSourceFactory.java
index b90a7e8aeb..6ab6ace818 100644
--- 
a/seatunnel-connectors-v2/connector-file/connector-file-hadoop/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/hdfs/source/HdfsFileSourceFactory.java
+++ 
b/seatunnel-connectors-v2/connector-file/connector-file-hadoop/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/hdfs/source/HdfsFileSourceFactory.java
@@ -113,6 +113,7 @@ public class HdfsFileSourceFactory implements 
TableSourceFactory {
                 .optional(
                         FileBaseSourceOptions.SHEET_NAME,
                         FileBaseSourceOptions.EXCEL_ENGINE,
+                        FileBaseSourceOptions.EXCEL_USE_HEADER_LINE,
                         FileBaseSourceOptions.POI_EXCEL_MAX_FILE_SIZE)
                 .conditional(
                         HdfsFileSourceOptions.FILE_FORMAT_TYPE,
diff --git 
a/seatunnel-connectors-v2/connector-file/connector-file-jindo-oss/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/oss/jindo/source/OssFileSourceFactory.java
 
b/seatunnel-connectors-v2/connector-file/connector-file-jindo-oss/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/oss/jindo/source/OssFileSourceFactory.java
index f94430a48f..a66194550d 100644
--- 
a/seatunnel-connectors-v2/connector-file/connector-file-jindo-oss/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/oss/jindo/source/OssFileSourceFactory.java
+++ 
b/seatunnel-connectors-v2/connector-file/connector-file-jindo-oss/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/oss/jindo/source/OssFileSourceFactory.java
@@ -94,6 +94,7 @@ public class OssFileSourceFactory implements 
TableSourceFactory {
                 .optional(
                         FileBaseSourceOptions.SHEET_NAME,
                         FileBaseSourceOptions.EXCEL_ENGINE,
+                        FileBaseSourceOptions.EXCEL_USE_HEADER_LINE,
                         FileBaseSourceOptions.POI_EXCEL_MAX_FILE_SIZE)
                 .conditional(
                         FileBaseSourceOptions.FILE_FORMAT_TYPE,
diff --git 
a/seatunnel-connectors-v2/connector-file/connector-file-local/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/local/source/LocalFileSourceFactory.java
 
b/seatunnel-connectors-v2/connector-file/connector-file-local/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/local/source/LocalFileSourceFactory.java
index b6ef0900ba..ce2f0f65a8 100644
--- 
a/seatunnel-connectors-v2/connector-file/connector-file-local/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/local/source/LocalFileSourceFactory.java
+++ 
b/seatunnel-connectors-v2/connector-file/connector-file-local/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/local/source/LocalFileSourceFactory.java
@@ -113,6 +113,7 @@ public class LocalFileSourceFactory implements 
TableSourceFactory {
                 .optional(
                         FileBaseSourceOptions.SHEET_NAME,
                         FileBaseSourceOptions.EXCEL_ENGINE,
+                        FileBaseSourceOptions.EXCEL_USE_HEADER_LINE,
                         FileBaseSourceOptions.POI_EXCEL_MAX_FILE_SIZE)
                 .conditional(
                         FileBaseSourceOptions.FILE_FORMAT_TYPE,
diff --git 
a/seatunnel-connectors-v2/connector-file/connector-file-local/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/local/LocalFileFactoryTest.java
 
b/seatunnel-connectors-v2/connector-file/connector-file-local/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/local/LocalFileFactoryTest.java
index 20f32463c8..8ea1c03b49 100644
--- 
a/seatunnel-connectors-v2/connector-file/connector-file-local/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/local/LocalFileFactoryTest.java
+++ 
b/seatunnel-connectors-v2/connector-file/connector-file-local/src/test/java/org/apache/seatunnel/connectors/seatunnel/file/local/LocalFileFactoryTest.java
@@ -66,6 +66,10 @@ class LocalFileFactoryTest {
                 
optionRule.getOptionalOptions().contains(FileBaseSourceOptions.START_MODE));
         Assertions.assertTrue(
                 
optionRule.getOptionalOptions().contains(FileBaseSourceOptions.POST_SYNC_ACTION));
+        Assertions.assertTrue(
+                optionRule
+                        .getOptionalOptions()
+                        
.contains(FileBaseSourceOptions.EXCEL_USE_HEADER_LINE));
         Assertions.assertTrue(
                 
optionRule.getOptionalOptions().contains(FileBaseSourceOptions.BACKUP_PATH));
         Assertions.assertTrue(
diff --git 
a/seatunnel-connectors-v2/connector-file/connector-file-obs/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/obs/source/ObsFileSourceFactory.java
 
b/seatunnel-connectors-v2/connector-file/connector-file-obs/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/obs/source/ObsFileSourceFactory.java
index 23876525b3..fb19d0a949 100644
--- 
a/seatunnel-connectors-v2/connector-file/connector-file-obs/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/obs/source/ObsFileSourceFactory.java
+++ 
b/seatunnel-connectors-v2/connector-file/connector-file-obs/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/obs/source/ObsFileSourceFactory.java
@@ -84,6 +84,7 @@ public class ObsFileSourceFactory implements 
TableSourceFactory {
                 .optional(
                         FileBaseSourceOptions.SHEET_NAME,
                         FileBaseSourceOptions.EXCEL_ENGINE,
+                        FileBaseSourceOptions.EXCEL_USE_HEADER_LINE,
                         FileBaseSourceOptions.POI_EXCEL_MAX_FILE_SIZE)
                 .conditional(
                         FileBaseSourceOptions.FILE_FORMAT_TYPE,
diff --git 
a/seatunnel-connectors-v2/connector-file/connector-file-oss/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/oss/source/OssFileSourceFactory.java
 
b/seatunnel-connectors-v2/connector-file/connector-file-oss/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/oss/source/OssFileSourceFactory.java
index 634e0ce627..871093fed2 100644
--- 
a/seatunnel-connectors-v2/connector-file/connector-file-oss/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/oss/source/OssFileSourceFactory.java
+++ 
b/seatunnel-connectors-v2/connector-file/connector-file-oss/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/oss/source/OssFileSourceFactory.java
@@ -104,6 +104,7 @@ public class OssFileSourceFactory implements 
TableSourceFactory {
                 .optional(
                         FileBaseSourceOptions.SHEET_NAME,
                         FileBaseSourceOptions.EXCEL_ENGINE,
+                        FileBaseSourceOptions.EXCEL_USE_HEADER_LINE,
                         FileBaseSourceOptions.POI_EXCEL_MAX_FILE_SIZE)
                 .conditional(
                         FileBaseSourceOptions.FILE_FORMAT_TYPE,
diff --git 
a/seatunnel-connectors-v2/connector-file/connector-file-s3/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/s3/source/S3FileSourceFactory.java
 
b/seatunnel-connectors-v2/connector-file/connector-file-s3/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/s3/source/S3FileSourceFactory.java
index 03cb29e75c..6e60f416ea 100644
--- 
a/seatunnel-connectors-v2/connector-file/connector-file-s3/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/s3/source/S3FileSourceFactory.java
+++ 
b/seatunnel-connectors-v2/connector-file/connector-file-s3/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/s3/source/S3FileSourceFactory.java
@@ -137,6 +137,7 @@ public class S3FileSourceFactory implements 
TableSourceFactory, SupportSourceDry
                 .optional(
                         FileBaseSourceOptions.SHEET_NAME,
                         FileBaseSourceOptions.EXCEL_ENGINE,
+                        FileBaseSourceOptions.EXCEL_USE_HEADER_LINE,
                         FileBaseSourceOptions.POI_EXCEL_MAX_FILE_SIZE)
                 .conditional(
                         FileBaseSourceOptions.FILE_FORMAT_TYPE,
diff --git 
a/seatunnel-connectors-v2/connector-file/connector-file-sftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sftp/source/SftpFileSourceFactory.java
 
b/seatunnel-connectors-v2/connector-file/connector-file-sftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sftp/source/SftpFileSourceFactory.java
index 768c8f75ed..951d273504 100644
--- 
a/seatunnel-connectors-v2/connector-file/connector-file-sftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sftp/source/SftpFileSourceFactory.java
+++ 
b/seatunnel-connectors-v2/connector-file/connector-file-sftp/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/sftp/source/SftpFileSourceFactory.java
@@ -96,6 +96,7 @@ public class SftpFileSourceFactory implements 
TableSourceFactory {
                 .optional(
                         FileBaseSourceOptions.SHEET_NAME,
                         FileBaseSourceOptions.EXCEL_ENGINE,
+                        FileBaseSourceOptions.EXCEL_USE_HEADER_LINE,
                         FileBaseSourceOptions.POI_EXCEL_MAX_FILE_SIZE)
                 .conditional(
                         FileBaseSourceOptions.FILE_FORMAT_TYPE,
diff --git 
a/seatunnel-connectors-v2/connector-file/connector-file-smb/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/smb/source/SmbFileSourceFactory.java
 
b/seatunnel-connectors-v2/connector-file/connector-file-smb/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/smb/source/SmbFileSourceFactory.java
index c109039f83..c97d1599f6 100644
--- 
a/seatunnel-connectors-v2/connector-file/connector-file-smb/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/smb/source/SmbFileSourceFactory.java
+++ 
b/seatunnel-connectors-v2/connector-file/connector-file-smb/src/main/java/org/apache/seatunnel/connectors/seatunnel/file/smb/source/SmbFileSourceFactory.java
@@ -97,6 +97,7 @@ public class SmbFileSourceFactory implements 
TableSourceFactory {
                 .optional(
                         FileBaseSourceOptions.SHEET_NAME,
                         FileBaseSourceOptions.EXCEL_ENGINE,
+                        FileBaseSourceOptions.EXCEL_USE_HEADER_LINE,
                         FileBaseSourceOptions.POI_EXCEL_MAX_FILE_SIZE)
                 .conditional(
                         FileBaseSourceOptions.FILE_FORMAT_TYPE,
diff --git 
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-file-local-e2e/src/test/java/org/apache/seatunnel/e2e/connector/file/local/LocalFileIT.java
 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-file-local-e2e/src/test/java/org/apache/seatunnel/e2e/connector/file/local/LocalFileIT.java
index 80864b1ada..3eb325e074 100644
--- 
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-file-local-e2e/src/test/java/org/apache/seatunnel/e2e/connector/file/local/LocalFileIT.java
+++ 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-file-local-e2e/src/test/java/org/apache/seatunnel/e2e/connector/file/local/LocalFileIT.java
@@ -38,6 +38,8 @@ import org.apache.seatunnel.e2e.common.util.JobIdGenerator;
 
 import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
 import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
 
 import org.awaitility.Awaitility;
 import org.junit.jupiter.api.Assertions;
@@ -45,6 +47,7 @@ import org.junit.jupiter.api.TestTemplate;
 import org.testcontainers.containers.Container;
 import org.testcontainers.containers.GenericContainer;
 import 
org.testcontainers.shaded.com.github.dockerjava.core.command.ExecStartResultCallback;
+import org.testcontainers.utility.MountableFile;
 
 import com.github.dockerjava.api.command.ExecCreateCmdResponse;
 import io.airlift.compress.lzo.LzopCodec;
@@ -251,6 +254,18 @@ public class LocalFileIT extends TestSuiteBase {
                         
"/seatunnel/read/excel/name=tyrantlucifer/hobby=coding/e2e.xls",
                         container);
 
+                Path headerProjectionFile =
+                        Files.createTempFile("excel-header-projection-", 
".xlsx");
+                try {
+                    createExcelHeaderProjectionFile(headerProjectionFile);
+                    // Temporary files can be owner-only; allow the container 
process to read it.
+                    container.copyFileToContainer(
+                            MountableFile.forHostPath(headerProjectionFile, 
0644),
+                            
"/seatunnel/read/excel_header_projection/e2e.xlsx");
+                } finally {
+                    Files.deleteIfExists(headerProjectionFile);
+                }
+
                 Path xlsxZip =
                         convertToZipFile(
                                 Lists.newArrayList(
@@ -360,6 +375,8 @@ public class LocalFileIT extends TestSuiteBase {
         helper.execute("/excel/fake_to_local_excel.conf");
         helper.execute("/excel/local_excel_to_assert.conf");
         helper.execute("/excel/local_excel_projection_to_assert.conf");
+        
helper.execute("/excel/local_excel_header_projection_poi_to_assert.conf");
+        
helper.execute("/excel/local_excel_header_projection_easyexcel_to_assert.conf");
         helper.execute("/excel/special_excel_to_assert.conf");
         helper.execute("/excel/local_filter_excel_to_assert.conf");
         helper.execute("/excel/local_filter_regex_excel_to_assert.conf");
@@ -971,6 +988,20 @@ public class LocalFileIT extends TestSuiteBase {
         return path;
     }
 
+    private void createExcelHeaderProjectionFile(Path file) throws IOException 
{
+        try (XSSFWorkbook workbook = new XSSFWorkbook();
+                FileOutputStream output = new FileOutputStream(file.toFile())) 
{
+            Sheet sheet = workbook.createSheet();
+            sheet.createRow(0).createCell(0).setCellValue("header-b");
+            sheet.getRow(0).createCell(1).setCellValue("header-a");
+            sheet.getRow(0).createCell(2).setCellValue("header-c");
+            sheet.createRow(1).createCell(0).setCellValue("value-b");
+            sheet.getRow(1).createCell(1).setCellValue("value-a");
+            sheet.getRow(1).createCell(2).setCellValue("value-c");
+            workbook.write(output);
+        }
+    }
+
     public Path convertToZipFile(List<File> files, String name) throws 
IOException {
         if (files == null || files.isEmpty()) {
             throw new IllegalArgumentException("File list is empty or 
invalid");
diff --git 
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-file-local-e2e/src/test/resources/excel/local_excel_header_projection_easyexcel_to_assert.conf
 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-file-local-e2e/src/test/resources/excel/local_excel_header_projection_easyexcel_to_assert.conf
new file mode 100644
index 0000000000..77c57a073e
--- /dev/null
+++ 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-file-local-e2e/src/test/resources/excel/local_excel_header_projection_easyexcel_to_assert.conf
@@ -0,0 +1,61 @@
+#
+# 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.
+#
+
+env {
+  parallelism = 1
+  job.mode = "BATCH"
+}
+
+source {
+  LocalFile {
+    path = "/seatunnel/read/excel_header_projection/e2e.xlsx"
+    file_format_type = excel
+    excel_engine = EasyExcel
+    excel_use_header_line = true
+    read_columns = ["header-a", "header-c"]
+    schema {
+      fields {
+        "header-a" = string
+        "header-b" = string
+        "header-c" = string
+      }
+    }
+  }
+}
+
+sink {
+  Assert {
+    rules {
+      row_rules = [
+        { rule_type = MIN_ROW, rule_value = 1 },
+        { rule_type = MAX_ROW, rule_value = 1 }
+      ]
+      field_rules = [
+        {
+          field_name = "header-a"
+          field_type = string
+          field_value = [{ rule_type = NOT_NULL, equals_to = "value-a" }]
+        },
+        {
+          field_name = "header-c"
+          field_type = string
+          field_value = [{ rule_type = NOT_NULL, equals_to = "value-c" }]
+        }
+      ]
+    }
+  }
+}
diff --git 
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-file-local-e2e/src/test/resources/excel/local_excel_header_projection_poi_to_assert.conf
 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-file-local-e2e/src/test/resources/excel/local_excel_header_projection_poi_to_assert.conf
new file mode 100644
index 0000000000..d01cabfeea
--- /dev/null
+++ 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-file-local-e2e/src/test/resources/excel/local_excel_header_projection_poi_to_assert.conf
@@ -0,0 +1,61 @@
+#
+# 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.
+#
+
+env {
+  parallelism = 1
+  job.mode = "BATCH"
+}
+
+source {
+  LocalFile {
+    path = "/seatunnel/read/excel_header_projection/e2e.xlsx"
+    file_format_type = excel
+    excel_engine = POI
+    excel_use_header_line = true
+    read_columns = ["header-a", "header-c"]
+    schema {
+      fields {
+        "header-a" = string
+        "header-b" = string
+        "header-c" = string
+      }
+    }
+  }
+}
+
+sink {
+  Assert {
+    rules {
+      row_rules = [
+        { rule_type = MIN_ROW, rule_value = 1 },
+        { rule_type = MAX_ROW, rule_value = 1 }
+      ]
+      field_rules = [
+        {
+          field_name = "header-a"
+          field_type = string
+          field_value = [{ rule_type = NOT_NULL, equals_to = "value-a" }]
+        },
+        {
+          field_name = "header-c"
+          field_type = string
+          field_value = [{ rule_type = NOT_NULL, equals_to = "value-c" }]
+        }
+      ]
+    }
+  }
+}

Reply via email to