Master Excel Export in Spring Boot 3 with EasyExcel and Custom Annotations

This article demonstrates how to simplify and accelerate Excel export in Spring Boot 3 by using Alibaba's EasyExcel library together with a custom @ExportExcel annotation, a dedicated return‑value handler, and example controller code for static, dynamic, and template‑based exports.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Master Excel Export in Spring Boot 3 with EasyExcel and Custom Annotations

Introduction

Exporting data to Excel is a common requirement in Java projects, but traditional approaches often involve repetitive boilerplate code, low efficiency, and high maintenance cost. Alibaba's EasyExcel library provides a memory‑efficient, SAX‑based solution that excels at handling large data sets.

Custom Annotation

@Target(ElementType.METHOD)</code><code>@Retention(RetentionPolicy.RUNTIME)</code><code>public @interface ExportExcel {</code><code>    // Default file name; supports SpEL expressions</code><code>    String fileName() default "export";</code><code>    // Default sheet name; supports SpEL expressions</code><code>    String sheetName() default "Sheet";</code><code>    // Data model class</code><code>    Class<?> modelClass() default void.class;</code><code>    // Template path; supports SpEL expressions</code><code>    String template() default "";</code><code>}

The annotation is applied to controller methods; the method must return a List collection.

Return‑Value Handler

@Component</code><code>public class ExportExcelReturnValueHandler implements HandlerMethodReturnValueHandler, ApplicationContextAware {</code><code>    private final Pattern pattern = Pattern.compile("^#\\{(.*?)}$");</code><code>    private final ExpressionParser parser = new SpelExpressionParser();</code><code>    private final DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();</code><code>    private ApplicationContext context;</code><code>    @Override</code><code>    public boolean supportsReturnType(MethodParameter returnType) {</code><code>        return returnType.hasMethodAnnotation(ExportExcel.class);</code><code>    }</code><code>    @Override</code><code>    public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {</code><code>        ExportExcel annotation = returnType.getMethodAnnotation(ExportExcel.class);</code><code>        if (annotation == null) return;</code><code>        HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);</code><code>        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);</code><code>        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");</code><code>        response.setCharacterEncoding("utf-8");</code><code>        String fileName = evaluateExpression(returnType, annotation.fileName(), request);</code><code>        fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8).replaceAll("\\+", "%20") + ".xlsx";</code><code>        response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + fileName);</code><code>        if (returnValue instanceof List) {</code><code>            List<?> data = (List<?>) returnValue;</code><code>            Class<?> modelClass = annotation.modelClass();</code><code>            String template = annotation.template();</code><code>            Resource resource = StringUtils.hasLength(template) ? this.context.getResource(template) : null;</code><code>            String sheetName = evaluateExpression(returnType, annotation.sheetName(), request);</code><code>            if (modelClass != void.class) {</code><code>                ExcelWriterBuilder builder = EasyExcel.write(response.getOutputStream(), modelClass);</code><code>                if (resource != null && resource.exists()) {</code><code>                    builder.withTemplate(resource.getInputStream()).sheet().doFill(data);</code><code>                } else {</code><code>                    builder.autoCloseStream(true).sheet(sheetName).doWrite(data);</code><code>                }</code><code>            } else {</code><code>                ExcelWriterBuilder builder = EasyExcel.write(response.getOutputStream());</code><code>                if (resource != null && resource.exists()) {</code><code>                    builder.withTemplate(resource.getInputStream());</code><code>                }</code><code>                builder.autoCloseStream(true).sheet(sheetName).doWrite(data);</code><code>            }</code><code>            mavContainer.setRequestHandled(true);</code><code>        }</code><code>    }</code><code>    private String evaluateExpression(MethodParameter parameter, String expression, HttpServletRequest request) {</code><code>        Matcher matcher = pattern.matcher(expression);</code><code>        if (!matcher.matches()) return expression;</code><code>        expression = matcher.group(1);</code><code>        StandardEvaluationContext ctx = new MethodBasedEvaluationContext(null, parameter.getMethod(), new Object[0], parameterNameDiscoverer);</code><code>        ctx.setVariable("request", request);</code><code>        return parser.parseExpression(expression).getValue(ctx, String.class);</code><code>    }</code><code>    @Override</code><code>    public void setApplicationContext(ApplicationContext context) throws BeansException {</code><code>        this.context = context;</code><code>    }</code><code>}

Web Configuration

@Component</code><code>public class WebConfig implements WebMvcConfigurer {</code><code>    private final ExportExcelReturnValueHandler exportExcelHandler;</code><code>    public WebConfig(ExportExcelReturnValueHandler exportExcelHandler) {</code><code>        this.exportExcelHandler = exportExcelHandler;</code><code>    }</code><code>    @Override</code><code>    public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {</code><code>        handlers.add(this.exportExcelHandler);</code><code>    }</code><code>}

Entity Class

public class User {</code><code>    @ExcelProperty("编号")</code><code>    private Long id;</code><code>    @ExcelProperty("姓名")</code><code>    private String name;</code><code>    @ExcelProperty("年龄")</code><code>    private Integer age;</code><code>    @ExcelProperty(value = "性别", converter = SexConverter.class)</code><code>    private Integer sexCode;</code><code>}

Controller Example

@Controller</code><code>@RequestMapping("/exports")</code><code>public class ExportController {</code><code>    // Simple export</code><code>    @GetMapping("/simple")</code><code>    @ExportExcel(fileName = "简单数据导出")</code><code>    public List<Object> simpleExport() {</code><code>        return List.of(</code><code>            List.of("编号", "姓名", "年龄", "性别", "邮箱", "创建时间"),</code><code>            List.of(1, "张三", 22, 0),</code><code>            List.of(2, "李四", 33, 1)</code><code>        );</code><code>    }</code><code>    // Export using model class</code><code>    @GetMapping("/user")</code><code>    @ExportExcel(fileName = "用户数据导出", sheetName = "用户列表", modelClass = User.class)</code><code>    public List<User> exportWithModel() {</code><code>        List<User> list = new ArrayList<>();</code><code>        for (int i = 1; i <= 20; i++) {</code><code>            list.add(new User(i + 0L, "姓名 - " + i, new Random().nextInt(100), new Random().nextInt(2)));
        }</code><code>        return list;</code><code>    }</code><code>    // Dynamic file name export</code><code>    @GetMapping("/dynamic")</code><code>    @ExportExcel(fileName = "#{#request.getParameter('filename')}", sheetName = "动态数据")</code><code>    public List<List<Object>> dynamicExport() {</code><code>        return List.of(</code><code>            List.of("产品", "价格", "库存"),</code><code>            List.of("手机", 3999, 100)</code><code>        );</code><code>    }</code><code>    // Export using template</code><code>    @GetMapping("/template")</code><code>    @ExportExcel(fileName = "用户数据导出", sheetName = "用户列表", modelClass = User.class, template = "classpath:templates/user.xlsx")</code><code>    public List<User> exportWithTemplate() {</code><code>        List<User> list = new ArrayList<>();</code><code>        for (int i = 1; i <= 20; i++) {</code><code>            list.add(new User(i + 0L, "姓名 - " + i, new Random().nextInt(100), new Random().nextInt(2)));
        }</code><code>        return list;</code><code>    }</code><code>}

In real projects the controller simply delegates to a service layer for data retrieval. When dealing with massive data volumes, developers may need to further optimize the custom annotation and handler according to specific performance requirements.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Spring BootCustom Annotationeasyexcelexcel-export
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

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.