Backend Development 18 min read

Boost Your SpringBoot Apps with EasyExcel: Fast, Low-Memory Excel Import/Export

This article introduces Alibaba's EasyExcel library, demonstrates how to integrate it into SpringBoot, and provides step‑by‑step code examples for simple export, import, and complex one‑to‑many export using custom merge strategies, highlighting its performance advantages over EasyPoi.

macrozheng
macrozheng
macrozheng
Boost Your SpringBoot Apps with EasyExcel: Fast, Low-Memory Excel Import/Export
Operating Excel import/export is a common requirement; EasyExcel is a high‑performance, low‑memory Alibaba open‑source tool that outperforms EasyPoi for large data sets.

EasyExcel Introduction

EasyExcel is an Alibaba open‑source Excel import/export library known for speed, low memory consumption, and ease of use. It already has over 22k stars on GitHub, indicating its popularity.

EasyExcel can read a 75 MB file (460 000 rows × 25 columns) using only 64 MB of memory in 20 seconds; the ultra‑fast mode is even quicker.

Integration

Integrating EasyExcel into a SpringBoot project is straightforward—just add a single Maven dependency.
<code>&lt;!-- EasyExcel dependency --&gt;
&lt;dependency&gt;
    &lt;groupId&gt;com.alibaba&lt;/groupId&gt;
    &lt;artifactId&gt;easyexcel&lt;/artifactId&gt;
    &lt;version&gt;3.0.5&lt;/version&gt;
&lt;/dependency&gt;
</code>

Usage

EasyExcel and EasyPoi share a similar annotation‑driven usage model. Below are examples of simple single‑table export, import, and a complex one‑to‑many export.

Simple Export

Exporting member information demonstrates EasyExcel's basic export capability.

First, create a member entity class

Member

and annotate its fields.

<code>/**
 * Shopping member
 */
@Data
@EqualsAndHashCode(callSuper = false)
public class Member {
    @ExcelProperty("ID")
    @ColumnWidth(10)
    private Long id;
    @ExcelProperty("用户名")
    @ColumnWidth(20)
    private String username;
    @ExcelIgnore
    private String password;
    @ExcelProperty("昵称")
    @ColumnWidth(20)
    private String nickname;
    @ExcelProperty("出生日期")
    @ColumnWidth(20)
    @DateTimeFormat("yyyy-MM-dd")
    private Date birthday;
    @ExcelProperty("手机号")
    @ColumnWidth(20)
    private String phone;
    @ExcelIgnore
    private String icon;
    @ExcelProperty(value = "性别", converter = GenderConverter.class)
    @ColumnWidth(10)
    private Integer gender;
}
</code>

The core annotations are:

@ExcelProperty – sets the column header (value) and optional converter.

@ColumnWidth – defines column width.

@DateTimeFormat – formats date fields.

To map enum values (e.g., 0→"男", 1→"女"), implement a custom converter

GenderConverter

.

<code>/**
 * Excel gender converter
 */
public class GenderConverter implements Converter<Integer> {
    @Override
    public Class<?> supportJavaTypeKey() { return Integer.class; }
    @Override
    public CellDataTypeEnum supportExcelTypeKey() { return CellDataTypeEnum.STRING; }
    @Override
    public Integer convertToJavaData(ReadConverterContext<?> context) throws Exception {
        String cellStr = context.getReadCellData().getStringValue();
        if (StrUtil.isEmpty(cellStr)) return null;
        if ("男".equals(cellStr)) return 0;
        if ("女".equals(cellStr)) return 1;
        return null;
    }
    @Override
    public WriteCellData<?> convertToExcelData(WriteConverterContext<Integer> context) throws Exception {
        Integer cellValue = context.getValue();
        if (cellValue == null) return new WriteCellData<>("");
        if (cellValue == 0) return new WriteCellData<>("男");
        if (cellValue == 1) return new WriteCellData<>("女");
        return new WriteCellData<>("");
    }
}
</code>

Add a controller endpoint to export the member list and set the response headers for file download.

<code>@Controller
@Api(tags = "EasyExcelController", description = "EasyExcel import/export demo")
@RequestMapping("/easyExcel")
public class EasyExcelController {
    @SneakyThrows(IOException.class)
    @ApiOperation(value = "Export member list Excel")
    @RequestMapping(value = "/exportMemberList", method = RequestMethod.GET)
    public void exportMemberList(HttpServletResponse response) {
        setExcelRespProp(response, "会员列表");
        List<Member> memberList = LocalJsonUtil.getListFromJson("json/members.json", Member.class);
        EasyExcel.write(response.getOutputStream())
                .head(Member.class)
                .excelType(ExcelTypeEnum.XLSX)
                .sheet("会员列表")
                .doWrite(memberList);
    }
    private void setExcelRespProp(HttpServletResponse response, String rawFileName) throws UnsupportedEncodingException {
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        String fileName = URLEncoder.encode(rawFileName, "UTF-8").replaceAll("\\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
    }
}
</code>
Test the endpoint via Swagger; click the download button in the response to obtain the Excel file.
After download, you will see a standard Excel file containing the exported data.

Simple Import

Importing member information shows EasyExcel's import capability.
<code>@Controller
@Api(tags = "EasyExcelController", description = "EasyExcel import/export demo")
@RequestMapping("/easyExcel")
public class EasyExcelController {
    @SneakyThrows
    @ApiOperation("Import member list from Excel")
    @RequestMapping(value = "/importMemberList", method = RequestMethod.POST)
    @ResponseBody
    public CommonResult importMemberList(@RequestPart("file") MultipartFile file) {
        List<Member> memberList = EasyExcel.read(file.getInputStream())
                .head(Member.class)
                .sheet()
                .doReadSync();
        return CommonResult.success(memberList);
    }
}
</code>
Test the import endpoint in Swagger by uploading the previously exported Excel file; the parsed data will be returned.

Complex Export (One‑to‑Many)

EasyExcel can also handle complex exports such as an order list with nested product information.

Using EasyPoi

EasyPoi supports nested objects out of the box via the

@ExcelCollection

annotation.

Finding a Solution

Since EasyExcel does not natively support one‑to‑many export, we search the project's issues for a suitable approach.

Search the issues for "一对多" to find discussions about custom merge strategies.

Solution Idea

By flattening nested data and merging cells where the order ID is identical, we can achieve a one‑to‑many export.

Implementation Process

Create a flat export DTO

OrderData

that contains order and product fields, using

@ExcelProperty

with array values for multi‑level headers and

@CustomMerge

to mark mergeable columns.

<code>@Data
@EqualsAndHashCode(callSuper = false)
public class OrderData {
    @ExcelProperty("订单ID")
    @ColumnWidth(10)
    @CustomMerge(needMerge = true, isPk = true)
    private String id;
    @ExcelProperty("订单编码")
    @ColumnWidth(20)
    @CustomMerge(needMerge = true)
    private String orderSn;
    @ExcelProperty("创建时间")
    @ColumnWidth(20)
    @DateTimeFormat("yyyy-MM-dd")
    @CustomMerge(needMerge = true)
    private Date createTime;
    @ExcelProperty("收货地址")
    @CustomMerge(needMerge = true)
    @ColumnWidth(20)
    private String receiverAddress;
    @ExcelProperty({"商品信息", "商品编码"})
    @ColumnWidth(20)
    private String productSn;
    @ExcelProperty({"商品信息", "商品名称"})
    @ColumnWidth(20)
    private String name;
    @ExcelProperty({"商品信息", "商品标题"})
    @ColumnWidth(30)
    private String subTitle;
    @ExcelProperty({"商品信息", "品牌名称"})
    @ColumnWidth(20)
    private String brandName;
    @ExcelProperty({"商品信息", "商品价格"})
    @ColumnWidth(20)
    private BigDecimal price;
    @ExcelProperty({"商品信息", "商品数量"})
    @ColumnWidth(20)
    private Integer count;
}
</code>

Convert the original

Order

list to a list of

OrderData

by flattening each product.

<code>private List<OrderData> convert(List<Order> orderList) {
    List<OrderData> result = new ArrayList<>();
    for (Order order : orderList) {
        for (Product product : order.getProductList()) {
            OrderData orderData = new OrderData();
            BeanUtil.copyProperties(product, orderData);
            BeanUtil.copyProperties(order, orderData);
            result.add(orderData);
        }
    }
    return result;
}
</code>

Define the custom annotation

CustomMerge

to indicate merge requirements.

<code>@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface CustomMerge {
    /** Whether the cell needs merging */
    boolean needMerge() default false;
    /** Whether this field is the primary key for merging */
    boolean isPk() default false;
}
</code>

Implement a merge strategy

CustomMergeStrategy

that merges cells sharing the same primary‑key value.

<code>public class CustomMergeStrategy implements RowWriteHandler {
    private Integer pkIndex;
    private List<Integer> needMergeColumnIndex = new ArrayList<>();
    private Class<?> elementType;
    public CustomMergeStrategy(Class<?> elementType) { this.elementType = elementType; }
    @Override
    public void afterRowDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
                                Row row, Integer relativeRowIndex, Boolean isHead) {
        if (isHead) return;
        Sheet sheet = writeSheetHolder.getSheet();
        if (pkIndex == null) lazyInit(writeSheetHolder);
        if (row.getRowNum() <= 1) return;
        Row lastRow = sheet.getRow(row.getRowNum() - 1);
        if (lastRow.getCell(pkIndex).getStringCellValue()
                .equalsIgnoreCase(row.getCell(pkIndex).getStringCellValue())) {
            for (Integer colIdx : needMergeColumnIndex) {
                CellRangeAddress range = new CellRangeAddress(row.getRowNum() - 1, row.getRowNum(), colIdx, colIdx);
                sheet.addMergedRegionUnsafe(range);
            }
        }
    }
    private void lazyInit(WriteSheetHolder writeSheetHolder) {
        Sheet sheet = writeSheetHolder.getSheet();
        Row titleRow = sheet.getRow(0);
        Field[] fields = elementType.getDeclaredFields();
        for (Field field : fields) {
            ExcelProperty excelProp = field.getAnnotation(ExcelProperty.class);
            if (excelProp == null) continue;
            CustomMerge customMerge = field.getAnnotation(CustomMerge.class);
            if (customMerge == null) continue;
            for (int i = 0; i < fields.length; i++) {
                Cell cell = titleRow.getCell(i);
                if (cell == null) continue;
                if (excelProp.value()[0].equalsIgnoreCase(cell.getStringCellValue())) {
                    if (customMerge.isPk()) pkIndex = i;
                    if (customMerge.needMerge()) needMergeColumnIndex.add(i);
                }
            }
        }
        if (pkIndex == null) throw new IllegalStateException("@CustomMerge must specify a primary key");
    }
}
</code>

Add a controller endpoint to export the order list using the custom merge strategy.

<code>@Controller
@Api(tags = "EasyExcelController", description = "EasyExcel import/export demo")
@RequestMapping("/easyExcel")
public class EasyExcelController {
    @SneakyThrows
    @ApiOperation("Export order list Excel")
    @RequestMapping(value = "/exportOrderList", method = RequestMethod.GET)
    public void exportOrderList(HttpServletResponse response) {
        List<Order> orderList = getOrderList();
        List<OrderData> orderDataList = convert(orderList);
        setExcelRespProp(response, "订单列表");
        EasyExcel.write(response.getOutputStream())
                .head(OrderData.class)
                .registerWriteHandler(new CustomMergeStrategy(OrderData.class))
                .excelType(ExcelTypeEnum.XLSX)
                .sheet("订单列表")
                .doWrite(orderDataList);
    }
    // setExcelRespProp same as earlier
}
</code>
Test the endpoint via Swagger; the generated Excel file shows merged order ID cells for each product.

Other Uses

For deeper usage, refer to the official EasyExcel demo repository.

Summary

EasyExcel offers convenient, high‑performance Excel import/export for Java applications. While simple one‑to‑many exports require custom merge logic, the library excels with large data volumes compared to EasyPoi, which is more feature‑rich for smaller datasets.

References

Project repository: https://github.com/alibaba/easyexcel

Official documentation: https://www.yuque.com/easyexcel/doc/easyexcel

Source code for this tutorial: https://github.com/macrozheng/mall-learning/tree/master/mall-tiny-easyexcel

JavaperformanceSpringBooteasyexcelexcel-import
macrozheng
Written by

macrozheng

Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.