Headers
This chapter introduces how to write header data in spreadsheet.
Complex Header Writing
Overview
Supports setting multi-level headers by specifying main titles and subtitles through the @ExcelProperty annotation.
POJO Class
@Getter
@Setter
@EqualsAndHashCode
public class ComplexHeadData {
@ExcelProperty({"Main Title", "String Title"})
private String string;
@ExcelProperty({"Main Title", "Date Title"})
private Date date;
@ExcelProperty({"Main Title", "Number Title"})
private Double doubleData;
}
Code Example
@Test
public void complexHeadWrite() {
String fileName = "complexHeadWrite" + System.currentTimeMillis() + ".xlsx";
FesodSheet.write(fileName, ComplexHeadData.class)
.sheet()
.doWrite(data());
}
Result
| A | B | C | |
| 1 | Main Title | ||
| 2 | String Title | Date Title | Number Title |
| 3 | String0 | 2026-07-31 20:50:23 | 0.56 |
| 4 | String1 | 2026-07-31 20:50:23 | 0.56 |
| 5 | String2 | 2026-07-31 20:50:23 | 0.56 |
| ⋮ | … | … | … |
| 12 | String9 | 2026-07-31 20:50:23 | 0.56 |
Dynamic Header Writing
Overview
Generate dynamic headers in real-time, suitable for scenarios where header content changes dynamically.
Code Example
@Test
public void dynamicHeadWrite() {
String fileName = "dynamicHeadWrite" + System.currentTimeMillis() + ".xlsx";
List<List<String>> head = Arrays.asList(
Collections.singletonList("Dynamic String Title"),
Collections.singletonList("Dynamic Number Title"),
Collections.singletonList("Dynamic Date Title"));
FesodSheet.write(fileName)
.head(head)
.sheet()
.doWrite(data());
}
Result
| A | B | C | |
| 1 | Dynamic String Title | Dynamic Number Title | Dynamic Date Title |
| 2 | String0 | 0.56 | 2026-07-31 20:50:23 |
| 3 | String1 | 0.56 | 2026-07-31 20:50:23 |
| 4 | String2 | 0.56 | 2026-07-31 20:50:23 |
| ⋮ | … | … | … |
| 11 | String9 | 0.56 | 2026-07-31 20:50:23 |
Header Merge Strategy
Overview
By default, Fesod automatically merges header cells with the same name. However, you can control the merge behavior using the headerMergeStrategy parameter.
Merge Strategies
- NONE: No automatic merging is performed.
- HORIZONTAL_ONLY: Only merges cells horizontally (adjacent columns in the same header row).
- VERTICAL_ONLY: Only merges cells vertically (adjacent rows in the same column).
- FULL_RECTANGLE: Merges both directions, but only where the repeated names form a complete rectangle.
- AUTO: Merges in both directions (default), except that a vertical merge starting below the top header row also requires the cells directly above the two rows to match - see the note under the example.
Strategies only matter for a multi-level header, since merging is driven by the same name repeating in adjacent cells. The example below uses a three-level header where names repeat in both directions:
Code Example
@Test
public void dynamicHeadWriteWithStrategy() {
String fileName = "dynamicHeadWrite" + System.currentTimeMillis() + ".xlsx";
List<List<String>> head = Arrays.asList(
Arrays.asList("Main Title", "ID", "ID"),
Arrays.asList("Main Title", "Group A", "Name"),
Arrays.asList("Main Title", "Group A", "Age"),
Arrays.asList("Main Title", "Group B", "Name"),
Arrays.asList("Main Title", "Group B", "Age"));
FesodSheet.write(fileName)
.head(head)
.headerMergeStrategy(HeaderMergeStrategy.FULL_RECTANGLE)
.sheet()
.doWrite(data());
}
Each inner list is one column, listing its title from the top level down. So "Main Title" spans all five columns in row 1,
"Group A" covers columns B–C in row 2, and "ID" repeats down rows 2–3 of column A.
NONE
Every header cell stands alone:
| A | B | C | D | E | |
| 1 | Main Title | Main Title | Main Title | Main Title | Main Title |
| 2 | ID | Group A | Group A | Group B | Group B |
| 3 | ID | Name | Age | Name | Age |
HORIZONTAL_ONLY
Merges A1:E1, B2:C2, D2:E2. The repeated ID in column A stays split:
| A | B | C | D | E | |
| 1 | Main Title | ||||
| 2 | ID | Group A | Group B | ||
| 3 | ID | Name | Age | Name | Age |
VERTICAL_ONLY
Merges only A2:A3; the row-spanning titles stay split:
| A | B | C | D | E | |
| 1 | Main Title | Main Title | Main Title | Main Title | Main Title |
| 2 | ID | Group A | Group A | Group B | Group B |
| 3 | Name | Age | Name | Age |
FULL_RECTANGLE
Merges A1:E1, A2:A3, B2:C2, D2:E2:
| A | B | C | D | E | |
| 1 | Main Title | ||||
| 2 | ID | Group A | Group B | ||
| 3 | Name | Age | Name | Age | |
AUTO
Merges A1:E1, B2:C2, D2:E2, but not A2:A3:
| A | B | C | D | E | |
| 1 | Main Title | ||||
| 2 | ID | Group A | Group B | ||
| 3 | ID | Name | Age | Name | Age |
AUTO merges horizontally exactly like FULL_RECTANGLE. The two differ only in vertical merging, where
AUTO looks at where the repeated name starts:
- It starts in header row 1. The cells merge, with no extra condition, because there is no row above row 1 to
compare against. A column headed
ID/ID/IDbecomes one tall cell underAUTO, just as underFULL_RECTANGLE. - It starts further down. Two rows merge only if the cells directly above them hold the same name.
The example above is the second case. ID repeats in rows 2 and 3, so AUTO compares what sits above each of them:
above row 2 is Main Title, above row 3 is ID itself. They differ, so the two cells are left separate.
FULL_RECTANGLE makes no such comparison and merges A2:A3.
The condition is applied to each pair of rows in turn, so AUTO can merge just part of a repeated run. For a
column headed Main Title / B / B / B, AUTO merges rows 3–4 only (both sit under a B) and leaves row 2
on its own, where FULL_RECTANGLE merges rows 2–4 into one cell.
So if a column title that repeats down the levels should become one tall cell, choose FULL_RECTANGLE or
VERTICAL_ONLY explicitly.
Common Use Cases
Disable merging: Use NONE to completely disable automatic merging:
FesodSheet.write(fileName)
.head(head)
.headerMergeStrategy(HeaderMergeStrategy.NONE)
.sheet()
.doWrite(data());
Note: The old automaticMergeHead parameter is still supported for backward compatibility. When headerMergeStrategy is not set, the behavior is determined by automaticMergeHead (true → AUTO, false → NONE).