[JAVA POI] - The maximum number of Cell Styles was exceeded.

문제 상황

Apache POI를 사용하여 Excel 파일(.xlsx)을 생성하거나 수정할 때 다음과 같은 오류가 발생할 수 있습니다.

java.lang.IllegalStateException: The maximum number of Cell Styles was exceeded.
You can define up to 64000 style in a .xlsx Workbook

발생 원인

1. Cell Style 생성 방식의 문제

Excel의 .xlsx 파일 형식에서는 최대 64,000개의 Cell Style만 생성할 수 있습니다. 이 제한은 Excel 파일 포맷의 스펙에 정의된 하드 리미트입니다.

일반적으로 이 오류는 다음과 같은 상황에서 발생합니다:

// 잘못된 예시 - 반복문 안에서 매번 새로운 스타일 생성
for (int i = 0; i < rows.size(); i++) {
Row row = sheet.createRow(i);
Cell cell = row.createCell(0);

// 매 셀마다 새로운 스타일을 생성 (문제!)
CellStyle style = workbook.createCellStyle();
Font font = workbook.createFont();
font.setBold(true);
style.setFont(font);

cell.setCellStyle(style);
cell.setCellValue(data.get(i));
}

위 코드에서 10,000개의 행을 처리한다면, 10,000개의 동일한 스타일이 생성되어 메모리를 낭비하고 결국 64,000개 제한에 도달하게 됩니다.

2. .xlsx vs .xls 차이점

  • .xls (HSSF): 최대 4,000개의 Cell Style 제한
  • .xlsx (XSSF): 최대 64,000개의 Cell Style 제한

해결 방법

방법 1: Cell Style 재사용 (권장)

가장 효율적인 방법은 필요한 스타일을 미리 생성하고 재사용하는 것입니다.

// 올바른 예시 - 스타일을 미리 생성하고 재사용
Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet("Data");

// 스타일을 한 번만 생성
CellStyle boldStyle = workbook.createCellStyle();
Font boldFont = workbook.createFont();
boldFont.setBold(true);
boldStyle.setFont(boldFont);

CellStyle normalStyle = workbook.createCellStyle();
Font normalFont = workbook.createFont();
normalStyle.setFont(normalFont);

// 반복문에서 스타일 재사용
for (int i = 0; i < rows.size(); i++) {
Row row = sheet.createRow(i);
Cell cell = row.createCell(0);

// 조건에 따라 미리 생성된 스타일 재사용
if (i % 2 == 0) {
cell.setCellStyle(boldStyle);
} else {
cell.setCellStyle(normalStyle);
}

cell.setCellValue(data.get(i));
}

방법 2: Style Map 활용

여러 종류의 스타일이 필요한 경우, Map을 사용하여 스타일을 관리합니다.

public class ExcelStyleManager {
private final Workbook workbook;
private final Map<String, CellStyle> styleCache = new HashMap<>();

public ExcelStyleManager(Workbook workbook) {
this.workbook = workbook;
}

public CellStyle getStyle(String styleKey) {
return styleCache.computeIfAbsent(styleKey, key -> createStyle(key));
}

private CellStyle createStyle(String styleKey) {
CellStyle style = workbook.createCellStyle();
Font font = workbook.createFont();

switch (styleKey) {
case "BOLD":
font.setBold(true);
break;
case "ITALIC":
font.setItalic(true);
break;
case "HEADER":
font.setBold(true);
font.setFontHeightInPoints((short) 14);
style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
break;
default:
// normal style
break;
}

style.setFont(font);
return style;
}
}

// 사용 예시
ExcelStyleManager styleManager = new ExcelStyleManager(workbook);

for (int i = 0; i < rows.size(); i++) {
Row row = sheet.createRow(i);
Cell cell = row.createCell(0);

if (i == 0) {
cell.setCellStyle(styleManager.getStyle("HEADER"));
} else {
cell.setCellStyle(styleManager.getStyle("BOLD"));
}

cell.setCellValue(data.get(i));
}

방법 3: 조건부 스타일 생성 최소화

날짜, 숫자 포맷 등 동적으로 스타일을 생성해야 하는 경우, 필요한 패턴들을 미리 정의합니다.

// 잘못된 예시
for (Cell cell : cells) {
CellStyle dateStyle = workbook.createCellStyle();
dateStyle.setDataFormat(
createHelper.createDataFormat().getFormat("yyyy-mm-dd")
);
cell.setCellStyle(dateStyle);
}

// 올바른 예시
CreationHelper createHelper = workbook.getCreationHelper();
CellStyle dateStyle = workbook.createCellStyle();
dateStyle.setDataFormat(
createHelper.createDataFormat().getFormat("yyyy-mm-dd")
);

for (Cell cell : cells) {
cell.setCellStyle(dateStyle); // 재사용
}

Best Practices

  1. 스타일을 Workbook 생성 직후에 미리 정의

    • 필요한 모든 스타일을 초기화 단계에서 생성
  2. 스타일 팩토리 패턴 사용

    • StyleManager 클래스를 만들어 중앙 집중식으로 관리
  3. 스타일 개수 모니터링

    // 현재 생성된 스타일 개수 확인
    int numCellStyles = workbook.getNumCellStyles();
    System.out.println("현재 스타일 개수: " + numCellStyles);
  4. 클래스 레벨에서 스타일 관리

    public class ExcelGenerator {
    private final CellStyle headerStyle;
    private final CellStyle dataStyle;
    private final CellStyle numberStyle;

    public ExcelGenerator(Workbook workbook) {
    this.headerStyle = createHeaderStyle(workbook);
    this.dataStyle = createDataStyle(workbook);
    this.numberStyle = createNumberStyle(workbook);
    }

    // ... 나머지 코드
    }

성능 비교

방식 10,000행 처리 시 스타일 개수 메모리 사용량
매번 생성 10,000개 높음
재사용 3-5개 낮음
Share