엑셀 다운로드

Apache POI + ExcelUtil 기반 엑셀 생성 및 다운로드 구현 규칙

마지막 수정: 2026-05

엑셀 다운로드

기본 원칙

  • Controller 반환 타입: ResponseEntity<byte[]>
  • Content-Disposition 헤더로 파일명 지정
  • XSSFWorkbook, ByteArrayOutputStream 사용
  • ExcelUtil 유틸리티 메서드를 활용한다

의존성

// build.gradle
implementation 'org.apache.poi:poi-ooxml:5.2.5'

컨트롤러 엑셀 다운로드 처리

@RestController
@RequiredArgsConstructor
@Slf4j
@RequestMapping("/api/user")
public class UserController {

    private final UserExcelService userExcelService;

    /**
     * 사용자 목록 엑셀 다운로드
     */
    @PostMapping("/excel")
    public ResponseEntity<byte[]> downloadExcel(@RequestBody UserVo vo) {
        byte[] excel = userExcelService.createUserExcel(vo);

        String filename = "사용자목록_" + DateUtil.getCurrentDateTime("yyyyMMdd") + ".xlsx";
        String encodedFilename = URLEncoder.encode(filename, StandardCharsets.UTF_8)
                .replace("+", "%20");

        return ResponseEntity.ok()
                .header(HttpHeaders.CONTENT_DISPOSITION,
                        "attachment; filename*=UTF-8''" + encodedFilename)
                .contentType(MediaType.APPLICATION_OCTET_STREAM)
                .body(excel);
    }
}

Service에서 ExcelUtil 사용

@Service
@RequiredArgsConstructor
@Slf4j
public class UserExcelService {

    private final UserMapper userMapper;

    @Transactional(readOnly = true)
    public byte[] createUserExcel(UserVo vo) {
        List<UserVo> list = userMapper.getList(vo);

        try (XSSFWorkbook workbook = new XSSFWorkbook()) {
            Sheet sheet = workbook.createSheet("사용자 목록");

            // 열 너비 설정
            ExcelUtil.setWidthCell(sheet, 0, 3000);   // 사용자번호
            ExcelUtil.setWidthCell(sheet, 1, 5000);   // 사용자명
            ExcelUtil.setWidthCell(sheet, 2, 8000);   // 이메일
            ExcelUtil.setWidthCell(sheet, 3, 6000);   // 등록일시

            // 헤더 행 생성 (0번째 행)
            Row headerRow = sheet.createRow(0);
            ExcelUtil.setHeightRow(headerRow, 400);

            Cell h0 = headerRow.createCell(0);
            Cell h1 = headerRow.createCell(1);
            Cell h2 = headerRow.createCell(2);
            Cell h3 = headerRow.createCell(3);

            ExcelUtil.setStringValue(h0, "사용자번호");
            ExcelUtil.setStringValue(h1, "사용자명");
            ExcelUtil.setStringValue(h2, "이메일");
            ExcelUtil.setStringValue(h3, "등록일시");

            // 헤더 스타일
            ExcelUtil.setStyleFont(h0, true, 11);
            ExcelUtil.setStyleFont(h1, true, 11);
            ExcelUtil.setStyleFont(h2, true, 11);
            ExcelUtil.setStyleFont(h3, true, 11);
            ExcelUtil.setStyleRowCellColor(h0, IndexedColors.GREY_25_PERCENT);
            ExcelUtil.setStyleRowCellColor(h1, IndexedColors.GREY_25_PERCENT);
            ExcelUtil.setStyleRowCellColor(h2, IndexedColors.GREY_25_PERCENT);
            ExcelUtil.setStyleRowCellColor(h3, IndexedColors.GREY_25_PERCENT);
            ExcelUtil.setStyleAlignment(h0, HorizontalAlignment.CENTER, VerticalAlignment.CENTER);

            // 데이터 행 생성
            int rowIdx = 1;
            for (UserVo user : list) {
                Row row = sheet.createRow(rowIdx++);
                ExcelUtil.setNumberValue(row.createCell(0), user.getUserSn());
                ExcelUtil.setStringValue(row.createCell(1), user.getUserNm());
                ExcelUtil.setStringValue(row.createCell(2), user.getUserEmail());
                ExcelUtil.setDateValue(row.createCell(3), user.getRegDt(), "yyyy-MM-dd HH:mm:ss");
            }

            ByteArrayOutputStream out = new ByteArrayOutputStream();
            workbook.write(out);
            return out.toByteArray();

        } catch (IOException e) {
            log.error("엑셀 생성 실패", e);
            throw new RuntimeException("엑셀 파일 생성 중 오류가 발생했습니다.");
        }
    }
}

ExcelUtil 주요 메서드

메서드설명
setWidthCell(sheet, col, width)열 너비 설정
setHeightRow(row, height)행 높이 설정
setStringValue(cell, value)문자열 값 설정
setDateValue(cell, value, format)날짜 값 설정
setNumberValue(cell, value)숫자 값 설정
setCurrencyValue(cell, value)통화 값 설정 (천 단위 구분)
setStyleFont(cell, bold, size)폰트 스타일 설정
setStyleAlignment(cell, hAlign, vAlign)정렬 설정
setStyleRowCellColor(cell, color)배경색 설정
mergeRowCell(sheet, row, colStart, colEnd)셀 병합
setImage(sheet, imgPath, row, col)이미지 삽입

파일명 인코딩

한글 파일명은 RFC 5987 방식으로 인코딩한다.

// 한글 파일명 인코딩
String filename = "사용자목록_20260516.xlsx";
String encodedFilename = URLEncoder.encode(filename, StandardCharsets.UTF_8)
        .replace("+", "%20");

return ResponseEntity.ok()
        .header(HttpHeaders.CONTENT_DISPOSITION,
                "attachment; filename*=UTF-8''" + encodedFilename)
        .contentType(MediaType.APPLICATION_OCTET_STREAM)
        .body(excel);

보안 주의사항

  • 엑셀 데이터에 개인정보 포함 시 권한 검증 필수
  • 대용량 데이터 엑셀 생성은 비동기 처리 고려 (OOM 방지)

체크리스트

  • [ ] Controller 반환 타입: ResponseEntity<byte[]>
  • [ ] Content-Disposition 헤더에 attachment 지정
  • [ ] 한글 파일명 UTF-8 인코딩 (filename*=UTF-8'')
  • [ ] ExcelUtil 메서드 활용
  • [ ] XSSFWorkbook, ByteArrayOutputStream 사용
  • [ ] 다운로드 권한 검증
  • [ ] 엑셀 파일에 개인정보 포함 여부 확인