Skip to content

JQuick-Excel

Pure Java · Apache-2.0 · Built on Apache POI

Generate chart-rich Excel reports in pure JavaNo hand-written Workbook — mapping, formulas and charts all live in one XML file

One EXPORT WITH DSL block declares column mapping, number formats, rolling closing-stock formulas, total-row styling and a 3D area chart. On the Java side you build your data, obtain a strongly-typed service proxy and call one method — the framework drives Apache POI underneath, with xls/xlsx dual format, streaming export and 42 built-in themes out of the box.

The actual export produced by the code above — JQuick-Excel
The actual export produced by the code above

Not a mock-up: exported by the sample code — download the xlsx and the XML config to compare line by line

  • 0 linesof POI boilerplate
  • 10built-in GRAPH chart types
  • 20built-in validation rules
  • 42built-in export themes
7 linesAll the Java code it takes to export this inventory report
List<JQuickRow> rows = inventoryRows(); // 业务数据:月份、期初、入库、出库
JQuickParseHandler parser =
new JQuickExcelExportXmlParseFactory("forestGreen", rows, out);
JQuickFactory factory = new JQuickXmlFactory(parser,
"jquick/biz/graph/0107_graph_inventory-trend.xml");
Graph0107InventoryTrendService service =
factory.createApi(Graph0107InventoryTrendService.class);
service.exportInventoryTrend("field", "value"); // 图表、公式、样式全在 XML 里

Imports and data-construction helpers omitted; the service interface is proxied from the XML namespace, and each method name matches an excel entry in the XML.

XMLThe rule declaration behind the same output (XML DSL excerpt)
EXPORT WITH SHEET="库存量趋势", HEADER=true,
MAPPING={"a":"月份","b":"期初","c":"入库","d":"出库","e":"期末"},
FORMAT={"b":"#,##0","c":"#,##0","d":"#,##0","e":"#,##0"},
FORMULAS={E2:'B2+C2-D2', E3:'E2+C3-D3', E4:'E3+C4-D4', E5:'E4+C5-D5',
C6:'SUM(C2:C5)', D6:'SUM(D2:D5)', E6:'E5'},
STYLE={ROW 6:{bold:true, fillPattern:solid_foreground,
fillForegroundColor:lightYellow, alignment:right}},
GRAPH={TYPE=AREA3D, TITLE="库存量趋势图", CATEGORY_AXIS=月份, VALUE_AXIS=库存,
CATEGORIES=["1月","2月","3月","4月"],
SERIES=[{NAME=期末, DATA=[4800,5200,4600,5100]}]}

Mapping, formats, row-by-row rolling-balance formulas, total-row highlighting and the line chart are all declarative. This DSL is the complete rule set behind the screenshot above — download the raw file and use it as-is.

The same inventory report in hand-written Apache POI

Apache POI is an excellent low-level library and JQuick-Excel is built on top of it — the table compares how much code you write, not what is possible.

AspectJQuick-ExcelHand-written Apache POI
Getting startedOne EXPORT WITH DSL block for the whole sheetBuild Workbook / Sheet / Row / Cell by hand, layer by layer
Mapping & formatsOne MAPPING line plus one FORMAT linesetCellValue per column and manual DataFormat creation
Formulas & totalsFORMULAS declares per-row formulas and SUM totalssetCellFormula per cell while tracking row numbers yourself
ChartsOne GRAPH DSL block, 10 chart typesAssemble axes, data references and series with XDDF by hand
Validation20 built-in rules scoped to row / column / cellImplement validation logic and error reporting yourself
Themes & styles42 built-in export themes, referenced by nameHand-write CellStyle or maintain your own template files
Large filesStreaming export and batch consumption built inSwitch to SXSSF yourself and manage sliding windows and style caches

It is Apache POI underneath: JQuick-Excel folds the repetitive boilerplate into one XML file without giving up any POI capability. Both are Apache-2.0 and coexist happily.

JQuick-Excel is a lightweight Java framework for importing and exporting xls and xlsx workbooks. It combines XML service definitions with a declarative DSL to express field mapping, type conversion, data validation, formulas, styling, merge aggregation, charts and footers in one unified way.

XML definitions keep Excel rules aligned with the service contract: Java callers create a service proxy from the definition, then pass business data, streams and context values into the import or export operation. The framework does not mandate Spring — plain Java projects and Spring Boot projects use the same API. It is built on Apache POI, runs in pure Java, and can be used on Windows, Linux, macOS and domestic (Xinchuang) operating systems.

  • Dual-format support: works with both xls and xlsx workbooks.
  • Declarative rules: import/export rules are declared via IMPORT WITH and EXPORT WITH.
  • Field mapping and conversion: field mapping, display formatting and expression-based per-row value conversion.
  • 20 built-in validation rules, with explicit scopes for rows, columns, cells and rectangular ranges.
  • Formula support: math, date/time, text, logical and lookup formulas, written per cell, row, column or range.
  • 10 chart types, configured through the GRAPH DSL.
  • Styling: row, column, cell and region styles, including font, alignment, border and fill.
  • 9 merge aggregation strategies: maximum, minimum, average, sum, first value, last value, concatenation, count and fixed value.
  • 42 built-in export themes.
  • Large-file friendly: streaming (SXSSF) export, large-file import, batch consumption and cell style caching.
  • Exporting orders, users, reports and similar business data to Excel files for download and archiving.
  • Importing data from Excel files provided by third parties or operations staff, with header mapping, value conversion and validity checks.
  • Statistical reporting scenarios that require summary formulas, merged cells, charts and footers in the export result.
  • Import/export of high-row-count data where streaming writes and batched reads are needed to keep memory usage under control.

Requirements:

Item Requirement
JDK Java 8 or later
Workbook formats xls, xlsx
Underlying dependencies Apache POI, built-in lexical/syntax-analysis runtime, Javelin Core, JQuick XML proxy — all pure Java
<dependency>
<groupId>io.github.paohaijiao</groupId>
<artifactId>jquick-excel</artifactId>
<version>3.6.0</version>
</dependency>

Use the latest release on Maven Central as the version number.

implementation 'io.github.paohaijiao:jquick-excel:3.6.0'

A complete import with JQuick-Excel requires three parts: an XML definition on the classpath, a service interface, and the Java calling code.

First, create jquick-excel.xml in the resources directory:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE excels PUBLIC "-//PAOHAIJIAO//DTD API EXCEL 1.0//EN"
"classpath:paohaijiao/dtd/Jquick-excel.dtd">
<excels namespace="com.example.StudentService">
<excel name="importExcel" returnClass="java.util.List">
<![CDATA[
IMPORT WITH
SHEET="Students",
HEADER=true,
MAPPING={"ID":"id","Name":"name","Age":"age"}
]]>
</excel>
</excels>

Second, define a service interface with the same name as the XML namespace:

package com.example;
import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.xml.param.Param;
import java.util.List;
public interface StudentService {
List<JQuickRow> importExcel(@Param("field") String field, @Param("value") String value);
}

Third, create the proxy and invoke it:

package com.example;
import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.xml.factory.JQuickFactory;
import com.github.paohaijiao.xml.factory.JQuickXmlFactory;
import com.github.paohaijiao.xml.handler.JQuickParseHandler;
import com.github.paohaijiao.xml.im.JQuickExcelImportXmlParseFactory;
import java.io.InputStream;
import java.util.List;
public class SimpleImportDemo {
public static void main(String[] args) throws Exception {
try (InputStream input = SimpleImportDemo.class.getClassLoader()
.getResourceAsStream("students.xlsx")) {
JQuickParseHandler parser = new JQuickExcelImportXmlParseFactory(input);
JQuickFactory factory = new JQuickXmlFactory(parser, "jquick-excel.xml");
StudentService service = factory.createApi(StudentService.class);
List<JQuickRow> rows = service.importExcel("field", "value");
System.out.println("导入行数:" + rows.size());
}
}
}

Add an export definition to jquick-excel.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE excels PUBLIC "-//PAOHAIJIAO//DTD API EXCEL 1.0//EN"
"classpath:paohaijiao/dtd/Jquick-excel.dtd">
<excels namespace="com.example.StudentService">
<excel name="exportExcel" returnClass="void">
<![CDATA[
EXPORT WITH
SHEET="Students",
HEADER=true,
MAPPING={"id":"ID","name":"Name","age":"Age"}
]]>
</excel>
</excels>

The service interface and Java calling code:

package com.example;
import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.xml.param.Param;
import java.util.List;
public interface StudentService {
void exportExcel(@Param("field") String field, @Param("value") String value);
}
package com.example;
import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.util.JObjectConverter;
import com.github.paohaijiao.xml.factory.JQuickFactory;
import com.github.paohaijiao.xml.factory.JQuickXmlFactory;
import com.github.paohaijiao.xml.handler.JQuickParseHandler;
import com.github.paohaijiao.xml.ex.JQuickExcelExportXmlParseFactory;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class SimpleExportDemo {
public static void main(String[] args) throws Exception {
List<Map<String, Object>> data = new ArrayList<>();
Map<String, Object> student = new LinkedHashMap<>();
student.put("id", "1001");
student.put("name", "Alice");
student.put("age", 20);
data.add(student);
// 将 Map 数据转换为框架行对象
List<JQuickRow> rows = JQuickRow.toRows(JObjectConverter.convert(data));
try (OutputStream output = new FileOutputStream("students.xlsx")) {
JQuickParseHandler parser = new JQuickExcelExportXmlParseFactory(rows, output);
JQuickFactory factory = new JQuickXmlFactory(parser, "jquick-excel.xml");
StudentService service = factory.createApi(StudentService.class);
service.exportExcel("field", "value");
System.out.println("导出完成");
}
}
}

Import configuration (declarative parameters)

Section titled “Import configuration (declarative parameters)”

The fixed entry point of the import DSL is IMPORT WITH, with parameters separated by commas:

IMPORT WITH
SHEET="Students",
HEADER=true,
MAPPING={"ID":"id"},
TRANSFORM={"name":toUpper(${name})},
VALIDATION={C2:C4:{integer{required:true}}}
  • Purpose: selects the source worksheet to read.
  • Accepted values:
    • String: the worksheet name, written as SHEET="Students".
    • Integer: the worksheet position, counted from 1, written as SHEET=1.
    • When this parameter is omitted, the first worksheet in the workbook is read by default.
  • Example:
IMPORT WITH SHEET="Students", HEADER=true
  • Purpose: declares whether the first row is a header row.
  • Accepted values: boolean; HEADER=true means the first row is the header and data is read starting from the row below it.
  • Default behavior: when not explicitly declared, the header is treated as enabled.
  • Example:
IMPORT WITH SHEET="Students", HEADER=true
  • Purpose: maps the header text in the worksheet to field names in the result rows.
  • Accepted values: the key is the Excel header text and the value is the target field name; the whole structure is a JSON-style mapping.
  • Direction: {"header":"field"}; for example, {"ID":"id"} means the column whose header is ID is read with id as its field name.
  • Example:
IMPORT WITH
SHEET="Students",
HEADER=true,
MAPPING={"ID":"id","Name":"name","Age":"age"}

Besides the DSL mapping, the framework also provides three annotations for declaring the worksheet/column correspondence on an entity class:

Annotation Target Attributes
@JExcelSheet Class name: worksheet name; headRowNumber: number of header rows, defaults to 1
@JExcelColumn Field name: column title; index: column index, defaults to -1; format: display format; converter: column converter name
@JExcelIgnore Field Marker annotation; annotated fields do not participate in import/export
import com.github.paohaijiao.anno.JExcelColumn;
import com.github.paohaijiao.anno.JExcelIgnore;
import com.github.paohaijiao.anno.JExcelSheet;
import java.util.Date;
@JExcelSheet(name = "学生信息", headRowNumber = 2)
public class StudentModel {
@JExcelColumn(name = "学号", index = 0)
private String id;
@JExcelColumn(name = "姓名", index = 1)
private String name;
@JExcelColumn(name = "入学日期", index = 4, format = "yyyy-MM-dd", converter = "date")
private Date enrollmentDate;
// 不参与导入导出的字段
@JExcelIgnore
private String ignoreField;
}
  • Purpose: applies a transform expression to a specified field of each read row before writing it into the result row.
  • Accepted values: the key is the target field name and the value is the transform expression.
  • Variable syntax:
    • ${field}: reads the value of the specified field in the current row, e.g. ${name}.
    • ${key}: reads an external value stored under that key in JContext, e.g. ${dict}.
  • Built-in transform functions:
    • toUpper(${name}): converts text to uppercase.
    • dateFormat(${birthday},'yyyy-MM-dd'): formats a date value with the given pattern.
    • trans(${dict},${sex}): maps a value using a dictionary in JContext.
  • Example:
IMPORT WITH
SHEET="Sheet1",
HEADER=true,
MAPPING={"学号":"no","姓名":"name","性别":"sex","出生日期":"birthday"},
TRANSFORM={
"sex":trans(${dict},${sex}),
"birthday":dateFormat(${birthday},'yyyy-MM-dd')
}

How to build the JContext dictionary:

import com.github.paohaijiao.param.JContext;
import java.util.HashMap;
import java.util.Map;
public class ContextDemo {
public static JContext buildContext() {
Map<String, Object> gender = new HashMap<>();
gender.put("男", "1");
gender.put("女", "2");
JContext context = new JContext();
context.put("dict", gender);
return context;
}
}

The external dependency jquick-transform-function also provides additional functions such as formatDate and translate through Java SPI; these functions can only be used in TRANSFORM after that dependency is explicitly introduced.

  • Purpose: applies data validation rules to specified ranges before the import results are consumed.
  • Accepted values: declared in the form “range + rule block”; inside a rule block you can configure required, msg and map:
    • required: whether the value is required (boolean).
    • msg: custom message used when validation fails; when not provided, the rule’s default message is used.
    • map: the parameter set required by the rule itself, such as upper/lower bounds or a format pattern.
  • Validation range syntax:
Range Meaning Example
Row A single row or a row interval ROW 5, ROW 1..10
Column A single column or a column interval COL A, COL A..D
Cell A single cell C1
Rectangular region A rectangular cell region A1:B5
  • Built-in validation rules (20 in total):
Rule Purpose Common map parameters
boolean Validates boolean values ——
date_format Validates date string format format
max_date Validates the upper date bound format、maxDate
min_date Validates the lower date bound format、minDate
integer Validates integers ——
decimal Validates decimals ——
max_value Validates the upper numeric bound maxValue
min_value Validates the lower numeric bound minValue
dict Validates dictionary membership Dictionary key-value pairs
email Validates email addresses ——
mobile Validates mobile phone numbers ——
max_length Validates the maximum string length maxLength
min_length Validates the minimum string length minLength
regex Validates against a regular expression pattern
start_with Requires a specified prefix startWith
not_start_with Forbids a specified prefix notStartWith
end_with Requires a specified suffix endWith
not_end_with Forbids a specified suffix notEndWith
contain Requires containing a substring contains
not_contain Forbids containing a substring notContain
  • Example:
IMPORT WITH VALIDATION={
ROW 2..100:{
integer{required:true,msg:'年龄必须是整数'}
},
C2:C100:{
email{required:true,msg:'邮箱格式无效'}
}
}
  • Behavior on validation failure: when validation fails, an exception is thrown whose message is the configured msg; when msg is not configured, the default message of the corresponding rule is used.
IMPORT WITH
HEADER=true,
SHEET='Sheet1',
MAPPING={
"学号": "no",
"姓名": "name",
"性别": "sex",
"年龄": "age",
"出生日期": "birthday"
},
TRANSFORM={
"sex":trans(${dict},${sex}),
"birthday":dateFormat(${birthday},'yyyy-MM-dd')
},
VALIDATION={
ROW 2..100:{
integer{required:true,msg:'年龄必须是整数'}
}
}

Export configuration (declarative parameters)

Section titled “Export configuration (declarative parameters)”

The fixed entry point of the export DSL is EXPORT WITH, with parameters separated by commas:

EXPORT WITH
SHEET="Report",
HEADER=true,
MAPPING={"id":"ID","name":"Name","amount":"Amount"},
FORMAT={"amount":"currency"},
TRANSFORM={"name":toUpper(${name})},
FORMULAS={D2:'SUM(B2:C2)'},
STYLE={ROW 1:{bold:true}},
MERGE:{ROWS 1..1,COLS A..D WITH FIRST},
GRAPH={TYPE=PIE,TITLE="Revenue",CATEGORIES=["Q1"],SERIES=[{NAME="2023",DATA=[1]}]},
FOOTER="Generated by JQuickExcel"
  • Purpose: selects the target worksheet into which export data is written.
  • Accepted values: a string is the worksheet name; an integer position is counted from 1; when omitted, the first worksheet is used.
  • Behavior: when the worksheet specified by name does not exist, a new worksheet is created with that name.
  • Example:
EXPORT WITH SHEET="Report", HEADER=true
  • Purpose: controls whether a header row is generated automatically from the mapping.
  • Accepted values: boolean; HEADER=true means the header is generated.
  • Default behavior: when not explicitly declared, the header is generated by default.
  • Example:
EXPORT WITH SHEET="Report", HEADER=true
  • Purpose: layers an Excel number display format (date, thousands separator, percentage, currency, etc.) on the data cells corresponding to the specified fields.
  • Syntax: FORMAT={"fieldName":"formatCode"}; multiple fields can be declared at once, separated by commas.
  • Accepted values:
Position Description Required
Key (field name) Source data field name, case-sensitive, consistent with the MAPPING keys and the keys in JQuickRow Yes
Value (format code) Standard Excel number format code, passed as-is to POI DataFormat.getFormat(); an empty string means no format is set Yes

Common format codes:

Option Description Usage
yyyy-MM-dd Date, year-month-day FORMAT={"enrollmentDate":"yyyy-MM-dd"}
yyyy"年"m"月"d"日" Chinese-style date FORMAT={"enrollmentDate":"yyyy\"年\"m\"月\"d\"日\""}
yyyy-MM-dd HH:mm Date + hour/minute (24-hour clock) FORMAT={"createdAt":"yyyy-MM-dd HH:mm"}
#,##0 Integer with thousands separator FORMAT={"quantity":"#,##0"}
#,##0.00 Number with thousands separator and fixed two decimals FORMAT={"amount":"#,##0.00"}
0.00 Number with fixed two decimals, no thousands separator FORMAT={"score":"0.00"}
0.00% Percentage (the underlying number is multiplied by 100 for display) FORMAT={"rate":"0.00%"}
¥#,##0.00 RMB currency format FORMAT={"amount":"¥#,##0.00"}
"$"#,##0.00 US dollar currency format FORMAT={"amount":"\"$\"#,##0.00"}
000000 Zero-padded number (e.g. 6-digit employee ID, postal code) FORMAT={"empNo":"000000"}
  • Example:
EXPORT WITH
FORMAT={"amount":"¥#,##0.00","enrollmentDate":"yyyy-MM-dd"}
  • Purpose: maps source data fields to header text in the exported worksheet.
  • Syntax: MAPPING={"sourceField":"headerText"}; the direction is “field → header”, and multiple entries can be declared at once.
  • Accepted values:
Option Description Usage
"源字段":"表头文本" Renames that column’s header to the specified text MAPPING={"id":"主键","name":"姓名"}
Fields not declared in MAPPING The column is still exported; by default its header directly uses the field name itself MAPPING={"id":"主键"} (other columns keep their field names)
  • Example:
EXPORT WITH
SHEET="Students",
HEADER=true,
MAPPING={"id":"ID","name":"Name","age":"Age"}
  • Purpose: before a value is written to a cell, evaluates a transform expression for the specified field of each row and replaces the original value with the computed result.
  • Syntax: TRANSFORM={"fieldName":function call}; multiple fields can be declared at once.
  • Parameter sources: ${field} reads the current row’s field value; ${key} reads a value preset in JContext.
  • Lookup order: providers are first searched by function name in the SPI registry (JQuickMethodInvocationManager); if no provider matches, it falls back to the built-in evaluator.

Built-in evaluator functions:

Option Description Usage
toUpper Converts text to uppercase TRANSFORM={"name":toUpper(${name})}
dateFormat Formats a date value with a pattern TRANSFORM={"enrollmentDate":dateFormat(${enrollmentDate},'yyyy-MM-dd')}
trans Maps a value through a dictionary in JContext TRANSFORM={"gender":trans(${dict},${gender})}

SPI extension functions (jquick-transform-function)

Section titled “SPI extension functions (jquick-transform-function)”

In addition to the built-in functions, the standalone module jquick-transform-function provides 248 functions (such as formatDate, translate, maskEmail and idCardValidate) through Java SPI. For the parameters, return values and full usage of each function, refer to that repository’s documentation as the authoritative source. Function extensions uniformly follow the SPI contract:

<dependency>
<groupId>io.github.paohaijiao</groupId>
<artifactId>jquick-transform-function</artifactId>
<version>1.4.0</version>
</dependency>

The SPI contract (com.github.paohaijiao.function.core.JQuickMethodFunctionProvider):

Option Description Usage
getMethodName() Declares the function name; TRANSFORM looks it up by this name Returns "formatDate"
invoke(List<Object> args) Receives the evaluated arguments and returns the result return args.get(0);
getPriority() Loading priority, default 5000; the smaller number wins default int getPriority()
META-INF/services/...JQuickMethodFunctionProvider SPI service descriptor file, listing the fully qualified names of implementation classes line by line com.example.FullNameFunction

Function group catalog (the complete list and usage of all 248 functions is governed by the jquick-transform-function repository):

Option Description Representative usage
array Array and list checks isArray(value)
bit Bitwise operations bitAnd(a,b)、bitOr(a,b)、bitXor(a,b)
bool Boolean checks isBoolean(value)
business Bank card, email, gender, ID card and phone number processing and validation bankCardMask(cardNo)、idCardValidate(idCard)、phoneMask(phone)、isEmail(value)
collection Null checks, collection joining and length isEmpty(value)、join(list,delimiter)、size(value)
condition Comparison, range, branching and null defaults between(value,min,max)、caseWhen(...)、coalesce(...)、nvl(value,defaultValue)
convert Boolean, date and short type conversion toBoolean(value)、toDate(value,pattern)
date Date arithmetic, extraction, comparison and formatting addDays(date,days)、daysBetween(d1,d2)、formatDate(date,pattern)、now()、today()
geometry Geometry, vector, matrix and complex-number operations distance(x1,y1,x2,y2)、gcd(a,b)、factorial(n)、isPrime(n)
extra Type conversion, number formatting and type checking cast(value,targetClass)、formatNumber(number,pattern)、typeOf(value)
json JSON serialization toJson(value)
math Arithmetic, trigonometric functions, statistics and base conversion abs(value)、roundTo(value,places)、sum(...)、toHex(number)
random Random values, random choice and UUID randomInt(min,max)、randomChoice(list)、randomUUID()、shuffle(list)
string String search, replacement, case, masking and encoding substring(str,begin,end)、replaceAll(str,regex,rep)、maskEmail(email)、base64Encode(str)
translate Context dictionary translation translate(context,code,dictType,defaultValue)
  • Example:
EXPORT WITH
MAPPING={"name":"Name","gender":"Gender"},
TRANSFORM={
"name":toUpper(${name}),
"gender":trans(${dict},${gender})
}
  • Purpose: writes native Excel formulas to specified cells, rows or columns; the formulas are calculated when the workbook is opened by spreadsheet software (Excel/WPS).
  • Syntax: FORMULAS={target:'formula text'}; multiple targets can be declared at once, separated by commas; the formula text must be quoted (single or double quotes both work).
  • Runtime behavior: the framework writes formulas via POI Cell.setCellFormula(...); unrecognized functions are written as-is as custom formulas.

Formula target forms:

Option Description Usage
Cell C1 Writes a formula to a single cell FORMULAS={D5:'SUM(D2:D4)'}
Row target ROW 5 Writes the same formula to all used columns of the specified row (relative references shift per column) FORMULAS={ROW 5:'SUM(B2:C2)'}
Row interval ROW 1..10 Writes the formula to all used columns of every row within the interval FORMULAS={ROW 2..10:'SUM(B2:C2)'}
Column target COL A Writes a formula to the specified column FORMULAS={COL E:'D2*0.1'}
Column interval COL A..D Writes a formula to a column interval FORMULAS={COL E..F:'D2*0.1'}

Built-in formula functions (listed completely by category):

Math functions

Option Description Usage
ABS Absolute value ABS(D2)
AVERAGE Arithmetic mean AVERAGE(D2:D4)
COUNT Counts numeric cells COUNT(D2:D4)
MAX Maximum value MAX(D2:D4)
MIN Minimum value MIN(D2:D4)
POWER Exponentiation POWER(2,3)
RAND Returns a random value in [0,1) RAND()
RANK Rank of a value within a range RANK(D2,D2:D4)
ROUND Rounds to the given number of digits ROUND(3.14159,2)
SQRT Square root SQRT(4)
STDEV Sample standard deviation STDEV(D2:D4)
SUM Sums numbers SUM(D2:D4)

Date and time functions

Option Description Usage
DATE Builds a date from year/month/day (treated as a date-time when combined with TIME) DATE(2023,5,15)
DATETIME Creates a date-time value DATETIME(2023,5,15,14,30,0)
DAY Extracts the day from a date DAY(A1)
DAYS Calculates the number of days between two dates DAYS(A1,A2)
EDATE Shifts a date by months EDATE(A1,3)
EOMONTH Returns the month-end date after a month offset EOMONTH(A1,3)
HOUR Extracts the hour HOUR(A1)
MINUTE Extracts the minute MINUTE(A1)
MONTH Extracts the month MONTH(A1)
NETWORKDAYS Counts working days between two dates NETWORKDAYS(A1,A2)
NOW Current date and time NOW()
SECOND Extracts the second SECOND(A1)
TIME Builds a time from hour/minute/second TIME(14,30,0)
TODAY Current date TODAY()
WEEKDAY Returns the weekday number WEEKDAY(A1,2)
WEEKNUM Returns the week number within the year WEEKNUM(A1,1)
WORKDAY Shifts a date by working days WORKDAY(A1,3,A2)
YEAR Extracts the year YEAR(A1)

Text functions

Option Description Usage
CONCAT Concatenates multiple texts CONCAT(A1,B1)
CONCATENATE Concatenates multiple values (text/number/boolean) CONCATENATE("A",1,TRUE)
EXACT Case-sensitive comparison of two texts EXACT("a","A")
FIND Case-sensitive lookup of text position FIND("n",A1)
LEFT Takes text of the given length from the left LEFT(A1,2)
LEN Number of characters in the text LEN(A1)
LOWER Converts to lowercase LOWER(A1)
MID Takes text by start position and length MID(A1,2,3)
PROPER Capitalizes the first letter of each word PROPER(A1)
REPLACE Replaces text by position REPLACE(A1,2,2,"XY")
RIGHT Takes text of the given length from the right RIGHT(A1,2)
SEARCH Case-insensitive lookup of text position SEARCH("n",A1,3)
SUBSTITUTE Replaces text by content SUBSTITUTE(A1,"a","b")
TEXT Converts a value to text with a format code TEXT(C2,"0.0%")
TRIM Removes leading and trailing spaces TRIM(A1)
UPPER Converts to uppercase UPPER(A1)
VALUE Converts text to a number VALUE(A1)

Logical and lookup functions

Option Description Usage
IF Returns one value when the condition holds, another otherwise IF(D2>0,"Yes","No")
AND All conditions hold AND(A1>0,B1>0)
OR Any condition holds OR(A1>0,B1>0)
LOOKUP Vector lookup (finds in a single row/column and returns the corresponding value from another single row/column) LOOKUP(22,D2:D4,C2:C4)
  • Example:
EXPORT WITH FORMULAS={
D5:'SUM(D2:D4)',
E5:'ROUND(AVERAGE(D2:D4),2)',
F5:'IF(D2>0,RANK(D2,D2:D4),0)'
}
  • Purpose: applies styles to rows, columns, cells or regions.
  • Accepted values: the key is the style target and the value is a collection of style attributes.
  • Target syntax: ROW 1, COL A, cell A2, region A2:C100.
  • Style attributes:
Attribute Purpose Example value
fontName Font family Arial
fontHeightInPoints Font size (points) 12
bold Bold true
italic Italic true
color Font color yellow
alignment Horizontal alignment center
verticalAlignment Vertical alignment center
wrapText Automatic text wrapping true
borderLeft / borderRight / borderTop / borderBottom Borders on the four sides thin
fillPattern Fill pattern solid_foreground
fillForegroundColor Foreground fill color yellow
fillBackgroundColor Background fill color white
dataFormatString Cell display format "yyyy-MM-dd"

Border values: none, thin, medium, dashed, dotted, thick, double.

Fill pattern values: no_fill, solid_foreground, fine_dots, sparse_dots, thin_horz_bands, thin_vert_bands.

  • Example:
EXPORT WITH STYLE={
ROW 1:{
fontName:Arial,
fontHeightInPoints:12,
italic:true,
color:yellow,
bold:true
},
A2:C100:{
alignment:center,
wrapText:true,
borderBottom:thin,
fillForegroundColor:lightYellow
}
}
  • Purpose: defines a cell merge region and, after WITH, specifies the aggregation strategy for the value retained after merging.
  • Accepted values: the merge range is described with a row interval and a column interval.
  • Aggregation strategies (9):
Strategy Meaning
MAX Use the maximum value
MIN Use the minimum value
AVG Use the average value
SUM Sum the values
FIRST Use the first value
LAST Use the last value
CONCAT Concatenate the values
COUNT Count the number of values
VALUE Use a fixed value
  • Example:
EXPORT WITH MERGE:{
ROWS 1..1,
COLS A..F WITH FIRST
}
  • Purpose: inserts an Excel chart into the exported worksheet and describes the chart type, title, axes, categories and data series with a declarative structure.
  • Entry syntax: GRAPH = { option, option, ... }, with options separated by commas.

The full set of options:

Option Description Usage
TYPE Chart type, one of the chart type enum values TYPE = LINE
TITLE Chart title; the string must be quoted TITLE = "月度销售统计"
CATEGORY_AXIS Category axis (horizontal) title; an unquoted identifier, Chinese supported CATEGORY_AXIS = 月份
VALUE_AXIS Value axis (vertical) title; an unquoted identifier, Chinese supported VALUE_AXIS = 销售额
CATEGORIES Array of category values, corresponding one-to-one to data points; text must be quoted CATEGORIES = ["1月", "2月", "3月"]
SERIES Array of data series; each series object contains fixed NAME and DATA, in the order NAME first then DATA SERIES = [{NAME = 一季度, DATA = [10, 20, 30]}]
NAME Series name; an unquoted identifier, Chinese supported NAME = 产品A
DATA Array of series data points; accepts numbers only (integers or decimals) DATA = [120, 200.5, 150]

Supported chart types (10):

Option Description Usage
LINE Line chart for trend analysis TYPE = LINE
COLUMN Vertical column chart for comparison TYPE = COLUMN
BAR Horizontal bar chart for comparison TYPE = BAR
BAR3D 3D bar chart TYPE = BAR3D
PIE Pie chart for proportions TYPE = PIE
AREA Area chart for cumulative trends TYPE = AREA
AREA3D 3D area chart TYPE = AREA3D
SCATTER Scatter chart for correlation TYPE = SCATTER
RADAR Radar chart for multi-dimensional comparison TYPE = RADAR
SURFACE Surface data chart TYPE = SURFACE
  • Full example:
EXPORT WITH GRAPH = {
TYPE = COLUMN,
TITLE = "季度销售统计",
CATEGORY_AXIS = 产品,
VALUE_AXIS = 销量,
CATEGORIES = ["产品A", "产品B", "产品C"],
SERIES = [
{NAME = 第一季度, DATA = [120, 200, 150]},
{NAME = 第二季度, DATA = [180, 210, 190]}
]
}
  • Purpose: sets the worksheet footer text.
  • Accepted values: a string; expressions can be embedded in the text for rendering.
  • Example:
EXPORT WITH
FOOTER="Generated by JQuickExcel",
MAPPING={"name":"Name"}

Example with an expression:

EXPORT WITH
MAPPING={"name":"Name","gender":"Gender"},
TRANSFORM={"gender":trans(${dict},${gender})},
FOOTER="Generated by JQuickExcel on ${current_date()}"
EXPORT WITH
SHEET="学生表",
HEADER=true,
MAPPING={
"id":"主键",
"name":"姓名",
"gender":"性别",
"age":"年龄",
"enrollmentDate":"入学时间",
"className":"班级"
},
FORMAT={
"enrollmentDate":"yyyy-MM-dd"
},
TRANSFORM={
"gender":trans(${dict},${gender}),
"age":add(${age},1),
"enrollmentDate":dateFormat(${enrollmentDate},'yyyy-MM-dd')
},
FORMULAS={
D5:'ABS(D2)'
},
STYLE={
ROW 1:{
fontName:Arial,
fontHeightInPoints:12,
italic:true,
color:yellow,
bold:true
}
},
MERGE:{
ROWS 1..1,
COLS A..D WITH FIRST
},
GRAPH={
TYPE=PIE,
TITLE="Revenue",
CATEGORIES=["Q1"],
SERIES=[{NAME="2023",DATA=[1]}]
},
FOOTER="Generated by JQuickExcel"

Beyond the declarative parameters, JQuick-Excel ships 42 built-in export templates (themes). Each theme uniformly controls the colors, fonts and border styles of the header row, odd/even zebra-striped data rows, the large title row, formula cells and the footer. Theme codes are defined by the enum com.github.paohaijiao.theme.enums.JExcelThemeType. Themes are an export-only capability; imports are not affected.

Themes are not set through a DSL keyword. Instead, after the rules are parsed and before the export handler is created, specify the theme in one of the following two ways.

Option 1: call setTheme on JExcelExportModel.

package org.example;
import com.github.paohaijiao.executor.JQuickExcelCommonExportExecutor;
import com.github.paohaijiao.handler.JExcelExportHandler;
import com.github.paohaijiao.model.JExcelExportModel;
import com.github.paohaijiao.statement.JQuickRow;
import org.apache.poi.ss.usermodel.Workbook;
import java.io.FileOutputStream;
import java.util.List;
/**
* 导出主题使用示例 / export theme example.
*/
public class ThemeExportDemo {
public static void main(String[] args) throws Exception {
// 1. 解析声明式导出规则
String rule = "EXPORT WITH HEADER=true";
JQuickExcelCommonExportExecutor executor = new JQuickExcelCommonExportExecutor();
JExcelExportModel model = (JExcelExportModel) executor.execute(rule);
// 2. 创建导出处理器之前设置主题编码(不区分大小写)
model.setTheme("jade");
// 3. 准备业务数据行
List<JQuickRow> rows = prepareRows();
// 4. 创建处理器并导出
JExcelExportHandler handler = new JExcelExportHandler(model, rows);
Workbook workbook = handler.getWorkBook();
try (FileOutputStream os = new FileOutputStream("d:/test/theme.xlsx")) {
workbook.write(os);
}
}
private static List<JQuickRow> prepareRows() {
// 省略:按业务数据组装 JQuickRow 列表
return java.util.Collections.emptyList();
}
}

Option 2: when using the XML proxy factory, pass the theme code through the first constructor argument.

import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.xml.ex.JQuickExcelExportXmlParseFactory;
import java.io.OutputStream;
import java.util.List;
// rows 为业务数据行,os 为导出目标流
JQuickExcelExportXmlParseFactory factory =
new JQuickExcelExportXmlParseFactory("jade", rows, os);
// 后续按 XML 代理方式 createApi(...) 并调用接口方法即可

The 42 built-in theme codes and names are as follows:

Option Description Usage
default Classic royal blue (default theme) setTheme("default")
oceanBlue Ocean blue setTheme("oceanBlue")
forestGreen Forest green setTheme("forestGreen")
sunsetOrange Sunset orange setTheme("sunsetOrange")
lavenderPurple Lavender purple setTheme("lavenderPurple")
sakuraPink Sakura pink setTheme("sakuraPink")
midnightDark Midnight dark setTheme("midnightDark")
minimalistGrey Minimalist grey setTheme("minimalistGrey")
vintageSepia Vintage sepia setTheme("vintageSepia")
tropicalTeal Tropical teal setTheme("tropicalTeal")
royalGold Royal gold setTheme("royalGold")
mintFresh Mint fresh setTheme("mintFresh")
amber Amber setTheme("amber")
amethyst Amethyst setTheme("amethyst")
azure Azure setTheme("azure")
charcoal Charcoal setTheme("charcoal")
coral Coral setTheme("coral")
crimsonRed Crimson red setTheme("crimsonRed")
cyan Cyan setTheme("cyan")
emerald Emerald setTheme("emerald")
espresso Espresso setTheme("espresso")
indigo Indigo setTheme("indigo")
mustard Mustard setTheme("mustard")
navyBlue Navy blue setTheme("navyBlue")
oliveGreen Olive green setTheme("oliveGreen")
peach Peach setTheme("peach")
sage Sage setTheme("sage")
skyBlue Sky blue setTheme("skyBlue")
steelBlue Steel blue setTheme("steelBlue")
terracotta Terracotta setTheme("terracotta")
turquoise Turquoise setTheme("turquoise")
wineRed Wine red setTheme("wineRed")
plum Plum setTheme("plum")
slate Slate setTheme("slate")
pearl Pearl setTheme("pearl")
bronze Bronze setTheme("bronze")
roseQuartz Rose quartz setTheme("roseQuartz")
jade Jade setTheme("jade")
champagne Champagne setTheme("champagne")
denim Denim setTheme("denim")
mahogany Mahogany setTheme("mahogany")
periwinkle Periwinkle setTheme("periwinkle")

The following tables show the actual export result of the 42 themes grouped by color family. Each row provides the theme code, theme name and preview image for direct comparison and selection.

Classic and grey family

Option (code) Description (name) Usage (preview)
default Classic royal blue default Classic royal blue
minimalistGrey Minimalist grey minimalistGrey Minimalist grey
slate Slate slate Slate

Dark and ocean-blue family

Option (code) Description (name) Usage (preview)
charcoal Charcoal charcoal Charcoal
navyBlue Navy blue navyBlue Navy blue
oceanBlue Ocean blue oceanBlue Ocean blue

Light blue and steel-blue family

Option (code) Description (name) Usage (preview)
skyBlue Sky blue skyBlue Sky blue
azure Azure azure Azure
steelBlue Steel blue steelBlue Steel blue

Denim and indigo family

Option (code) Description (name) Usage (preview)
denim Denim denim Denim
indigo Indigo indigo Indigo
periwinkle Periwinkle periwinkle Periwinkle

Forest green and jade family

Option (code) Description (name) Usage (preview)
forestGreen Forest green forestGreen Forest green
emerald Emerald emerald Emerald
jade Jade jade Jade

Mint and low-saturation green family

Option (code) Description (name) Usage (preview)
mintFresh Mint fresh mintFresh Mint fresh
sage Sage sage Sage
oliveGreen Olive green oliveGreen Olive green

Teal and cyan family

Option (code) Description (name) Usage (preview)
tropicalTeal Tropical teal tropicalTeal Tropical teal
turquoise Turquoise turquoise Turquoise
cyan Cyan cyan Cyan

Orange and peach family

Option (code) Description (name) Usage (preview)
sunsetOrange Sunset orange sunsetOrange Sunset orange
coral Coral coral Coral
peach Peach peach Peach

Crimson, wine and sakura family

Option (code) Description (name) Usage (preview)
crimsonRed Crimson red crimsonRed Crimson red
wineRed Wine red wineRed Wine red
sakuraPink Sakura pink sakuraPink Sakura pink

Pink and purple family

Option (code) Description (name) Usage (preview)
roseQuartz Rose quartz roseQuartz Rose quartz
lavenderPurple Lavender purple lavenderPurple Lavender purple
amethyst Amethyst amethyst Amethyst

Plum and gold family

Option (code) Description (name) Usage (preview)
plum Plum plum Plum
royalGold Royal gold royalGold Royal gold
champagne Champagne champagne Champagne

Amber, mustard and bronze family

Option (code) Description (name) Usage (preview)
amber Amber amber Amber
mustard Mustard mustard Mustard
bronze Bronze bronze Bronze

Vintage sepia and dark brown family

Option (code) Description (name) Usage (preview)
vintageSepia Vintage sepia vintageSepia Vintage sepia
espresso Espresso espresso Espresso
mahogany Mahogany mahogany Mahogany

Terracotta, dark and pearl family

Option (code) Description (name) Usage (preview)
terracotta Terracotta terracotta Terracotta
midnightDark Midnight dark midnightDark Midnight dark
pearl Pearl pearl Pearl

The following examples use the unified XML definition style. Each example provides the corresponding jquick-excel.xml definition and complete Java code.

XML definition:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE excels PUBLIC "-//PAOHAIJIAO//DTD API EXCEL 1.0//EN"
"classpath:paohaijiao/dtd/Jquick-excel.dtd">
<excels namespace="com.example.SheetNameService">
<excel name="readByName" returnClass="java.util.List">
<![CDATA[
IMPORT WITH
SHEET="Students",
HEADER=true
]]>
</excel>
</excels>

Service interface and calling code:

package com.example;
import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.xml.param.Param;
import java.util.List;
public interface SheetNameService {
List<JQuickRow> readByName(@Param("field") String field, @Param("value") String value);
}
package com.example;
import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.xml.factory.JQuickFactory;
import com.github.paohaijiao.xml.factory.JQuickXmlFactory;
import com.github.paohaijiao.xml.handler.JQuickParseHandler;
import com.github.paohaijiao.xml.im.JQuickExcelImportXmlParseFactory;
import java.io.InputStream;
import java.util.List;
public class ReadByNameDemo {
public static void main(String[] args) throws Exception {
try (InputStream input = ReadByNameDemo.class.getClassLoader()
.getResourceAsStream("students.xlsx")) {
JQuickParseHandler parser = new JQuickExcelImportXmlParseFactory(input);
JQuickFactory factory = new JQuickXmlFactory(parser, "jquick-excel.xml");
SheetNameService service = factory.createApi(SheetNameService.class);
List<JQuickRow> rows = service.readByName("field", "value");
rows.forEach(row -> System.out.println(row));
}
}
}

When SHEET uses an integer position, counting starts at 1. The following example reads the first worksheet:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE excels PUBLIC "-//PAOHAIJIAO//DTD API EXCEL 1.0//EN"
"classpath:paohaijiao/dtd/Jquick-excel.dtd">
<excels namespace="com.example.SheetIndexService">
<excel name="readByIndex" returnClass="java.util.List">
<![CDATA[
IMPORT WITH
SHEET=1,
HEADER=true
]]>
</excel>
</excels>
package com.example;
import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.xml.param.Param;
import java.util.List;
public interface SheetIndexService {
List<JQuickRow> readByIndex(@Param("field") String field, @Param("value") String value);
}
package com.example;
import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.xml.factory.JQuickFactory;
import com.github.paohaijiao.xml.factory.JQuickXmlFactory;
import com.github.paohaijiao.xml.handler.JQuickParseHandler;
import com.github.paohaijiao.xml.im.JQuickExcelImportXmlParseFactory;
import java.io.InputStream;
import java.util.List;
public class ReadByIndexDemo {
public static void main(String[] args) throws Exception {
try (InputStream input = ReadByIndexDemo.class.getClassLoader()
.getResourceAsStream("students.xlsx")) {
JQuickParseHandler parser = new JQuickExcelImportXmlParseFactory(input);
JQuickFactory factory = new JQuickXmlFactory(parser, "jquick-excel.xml");
SheetIndexService service = factory.createApi(SheetIndexService.class);
List<JQuickRow> rows = service.readByIndex("field", "value");
System.out.println("读取行数:" + rows.size());
}
}
}

Import with field mapping and value conversion

Section titled “Import with field mapping and value conversion”

XML definition:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE excels PUBLIC "-//PAOHAIJIAO//DTD API EXCEL 1.0//EN"
"classpath:paohaijiao/dtd/Jquick-excel.dtd">
<excels namespace="com.example.TransformService">
<excel name="importWithTransform" returnClass="java.util.List">
<![CDATA[
IMPORT WITH
HEADER=true,
SHEET='Sheet1',
MAPPING={
"学号": "no",
"姓名": "name",
"性别": "sex",
"出生日期": "birthday"
},
TRANSFORM={
"sex":trans(${dict},${sex}),
"birthday":dateFormat(${birthday},'yyyy-MM-dd')
}
]]>
</excel>
</excels>
package com.example;
import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.xml.param.Param;
import java.util.List;
public interface TransformService {
List<JQuickRow> importWithTransform(@Param("field") String field, @Param("value") String value);
}
package com.example;
import com.github.paohaijiao.param.JContext;
import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.xml.factory.JQuickFactory;
import com.github.paohaijiao.xml.factory.JQuickXmlFactory;
import com.github.paohaijiao.xml.handler.JQuickParseHandler;
import com.github.paohaijiao.xml.im.JQuickExcelImportXmlParseFactory;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class TransformImportDemo {
public static void main(String[] args) throws Exception {
Map<String, Object> gender = new HashMap<>();
gender.put("男", "1");
gender.put("女", "2");
JContext context = new JContext();
context.put("dict", gender);
try (InputStream input = TransformImportDemo.class.getClassLoader()
.getResourceAsStream("templates/student.xlsx")) {
JQuickParseHandler parser = new JQuickExcelImportXmlParseFactory(context, input);
JQuickFactory factory = new JQuickXmlFactory(parser, "jquick-excel.xml");
TransformService service = factory.createApi(TransformService.class);
List<JQuickRow> rows = service.importWithTransform("field", "value");
rows.forEach(row -> {
// JQuickRow 提供按字段名取值的类型化方法
System.out.println(row.getString("name") + " / " + row.getString("sex"));
});
}
}
}

JQuickRow also provides toBean(Class) and the static method toBeanList(rows, Class), which can convert result rows directly into Java Beans:

import com.github.paohaijiao.statement.JQuickRow;
import java.util.List;
public class RowConvertDemo {
public static List<StudentModel> toBeans(List<JQuickRow> rows) {
return JQuickRow.toBeanList(rows, StudentModel.class);
}
}

XML definition:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE excels PUBLIC "-//PAOHAIJIAO//DTD API EXCEL 1.0//EN"
"classpath:paohaijiao/dtd/Jquick-excel.dtd">
<excels namespace="com.example.ValidationService">
<excel name="importWithValidation" returnClass="java.util.List">
<![CDATA[
IMPORT WITH
HEADER=true,
SHEET='Sheet1',
VALIDATION={
ROW 2..100:{
integer{required:true,msg:'年龄必须是整数'}
},
C2:C100:{
email{required:true,msg:'邮箱格式无效'}
}
}
]]>
</excel>
</excels>
package com.example;
import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.xml.param.Param;
import java.util.List;
public interface ValidationService {
List<JQuickRow> importWithValidation(@Param("field") String field, @Param("value") String value);
}
package com.example;
import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.xml.factory.JQuickFactory;
import com.github.paohaijiao.xml.factory.JQuickXmlFactory;
import com.github.paohaijiao.xml.handler.JQuickParseHandler;
import com.github.paohaijiao.xml.im.JQuickExcelImportXmlParseFactory;
import java.io.InputStream;
import java.util.List;
public class ValidationImportDemo {
public static void main(String[] args) {
try (InputStream input = ValidationImportDemo.class.getClassLoader()
.getResourceAsStream("students.xlsx")) {
JQuickParseHandler parser = new JQuickExcelImportXmlParseFactory(input);
JQuickFactory factory = new JQuickXmlFactory(parser, "jquick-excel.xml");
ValidationService service = factory.createApi(ValidationService.class);
List<JQuickRow> rows = service.importWithValidation("field", "value");
System.out.println("校验通过,导入行数:" + rows.size());
} catch (Exception exception) {
// 校验不通过时,异常信息为 DSL 中配置的 msg
System.err.println("数据校验失败:" + exception.getMessage());
}
}
}

XML definition:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE excels PUBLIC "-//PAOHAIJIAO//DTD API EXCEL 1.0//EN"
"classpath:paohaijiao/dtd/Jquick-excel.dtd">
<excels namespace="com.example.BasicExportService">
<excel name="export" returnClass="void">
<![CDATA[
EXPORT WITH
SHEET="Students",
HEADER=true,
MAPPING={"id":"ID","name":"Name","age":"Age"}
]>
</excel>
</excels>
package com.example;
import com.github.paohaijiao.xml.param.Param;
public interface BasicExportService {
void export(@Param("field") String field, @Param("value") String value);
}
package com.example;
import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.util.JObjectConverter;
import com.github.paohaijiao.xml.factory.JQuickFactory;
import com.github.paohaijiao.xml.factory.JQuickXmlFactory;
import com.github.paohaijiao.xml.handler.JQuickParseHandler;
import com.github.paohaijiao.xml.ex.JQuickExcelExportXmlParseFactory;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class BasicExportDemo {
public static void main(String[] args) throws Exception {
List<Map<String, Object>> data = new ArrayList<>();
Map<String, Object> row = new LinkedHashMap<>();
row.put("id", "1001");
row.put("name", "Alice");
row.put("age", 20);
data.add(row);
List<JQuickRow> rows = JQuickRow.toRows(JObjectConverter.convert(data));
try (OutputStream output = new FileOutputStream("students.xlsx")) {
JQuickParseHandler parser = new JQuickExcelExportXmlParseFactory(rows, output);
JQuickFactory factory = new JQuickXmlFactory(parser, "jquick-excel.xml");
BasicExportService service = factory.createApi(BasicExportService.class);
service.export("field", "value");
}
}
}

XML definition:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE excels PUBLIC "-//PAOHAIJIAO//DTD API EXCEL 1.0//EN"
"classpath:paohaijiao/dtd/Jquick-excel.dtd">
<excels namespace="com.example.StyleExportService">
<excel name="export" returnClass="void">
<![CDATA[
EXPORT WITH
SHEET="Report",
HEADER=true,
MAPPING={"id":"ID","name":"Name","amount":"Amount"},
FORMAT={"amount":"currency"},
STYLE={
ROW 1:{
fontName:Arial,
bold:true,
color:yellow
},
A2:C100:{
alignment:center,
borderBottom:thin
}
}
]]>
</excel>
</excels>
package com.example;
import com.github.paohaijiao.xml.param.Param;
public interface StyleExportService {
void export(@Param("field") String field, @Param("value") String value);
}
package com.example;
import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.util.JObjectConverter;
import com.github.paohaijiao.xml.factory.JQuickFactory;
import com.github.paohaijiao.xml.factory.JQuickXmlFactory;
import com.github.paohaijiao.xml.handler.JQuickParseHandler;
import com.github.paohaijiao.xml.ex.JQuickExcelExportXmlParseFactory;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class StyleExportDemo {
public static void main(String[] args) throws Exception {
List<Map<String, Object>> data = new ArrayList<>();
Map<String, Object> row = new LinkedHashMap<>();
row.put("id", "1001");
row.put("name", "Alice");
row.put("amount", 128.5);
data.add(row);
List<JQuickRow> rows = JQuickRow.toRows(JObjectConverter.convert(data));
try (OutputStream output = new FileOutputStream("report.xlsx")) {
JQuickParseHandler parser = new JQuickExcelExportXmlParseFactory(rows, output);
JQuickFactory factory = new JQuickXmlFactory(parser, "jquick-excel.xml");
StyleExportService service = factory.createApi(StyleExportService.class);
service.export("field", "value");
}
}
}

XML definition:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE excels PUBLIC "-//PAOHAIJIAO//DTD API EXCEL 1.0//EN"
"classpath:paohaijiao/dtd/Jquick-excel.dtd">
<excels namespace="com.example.MergeExportService">
<excel name="export" returnClass="void">
<![CDATA[
EXPORT WITH
HEADER=true,
MERGE:{
ROWS 1..1,
COLS A..F WITH FIRST
}
]]>
</excel>
</excels>
package com.example;
import com.github.paohaijiao.xml.param.Param;
public interface MergeExportService {
void export(@Param("field") String field, @Param("value") String value);
}
package com.example;
import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.util.JObjectConverter;
import com.github.paohaijiao.xml.factory.JQuickFactory;
import com.github.paohaijiao.xml.factory.JQuickXmlFactory;
import com.github.paohaijiao.xml.handler.JQuickParseHandler;
import com.github.paohaijiao.xml.ex.JQuickExcelExportXmlParseFactory;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class MergeExportDemo {
public static void main(String[] args) throws Exception {
List<Map<String, Object>> data = new ArrayList<>();
List<JQuickRow> rows = JQuickRow.toRows(JObjectConverter.convert(data));
try (OutputStream output = new FileOutputStream("merged.xlsx")) {
JQuickParseHandler parser = new JQuickExcelExportXmlParseFactory(rows, output);
JQuickFactory factory = new JQuickXmlFactory(parser, "jquick-excel.xml");
MergeExportService service = factory.createApi(MergeExportService.class);
service.export("field", "value");
}
}
}

XML definition:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE excels PUBLIC "-//PAOHAIJIAO//DTD API EXCEL 1.0//EN"
"classpath:paohaijiao/dtd/Jquick-excel.dtd">
<excels namespace="com.example.FormulaExportService">
<excel name="export" returnClass="void">
<![CDATA[
EXPORT WITH
FORMULAS={
D5:'SUM(D2:D4)',
E5:'ROUND(AVERAGE(D2:D4),2)',
F5:'RANK(D2,D2:D4)'
}
]]>
</excel>
</excels>
package com.example;
import com.github.paohaijiao.xml.param.Param;
public interface FormulaExportService {
void export(@Param("field") String field, @Param("value") String value);
}
package com.example;
import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.util.JObjectConverter;
import com.github.paohaijiao.xml.factory.JQuickFactory;
import com.github.paohaijiao.xml.factory.JQuickXmlFactory;
import com.github.paohaijiao.xml.handler.JQuickParseHandler;
import com.github.paohaijiao.xml.ex.JQuickExcelExportXmlParseFactory;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class FormulaExportDemo {
public static void main(String[] args) throws Exception {
List<Map<String, Object>> data = new ArrayList<>();
List<JQuickRow> rows = JQuickRow.toRows(JObjectConverter.convert(data));
try (OutputStream output = new FileOutputStream("formula.xlsx")) {
JQuickParseHandler parser = new JQuickExcelExportXmlParseFactory(rows, output);
JQuickFactory factory = new JQuickXmlFactory(parser, "jquick-excel.xml");
FormulaExportService service = factory.createApi(FormulaExportService.class);
service.export("field", "value");
}
}
}

XML definition:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE excels PUBLIC "-//PAOHAIJIAO//DTD API EXCEL 1.0//EN"
"classpath:paohaijiao/dtd/Jquick-excel.dtd">
<excels namespace="com.example.ChartExportService">
<excel name="export" returnClass="void">
<![CDATA[
EXPORT WITH GRAPH = {
TYPE = PIE,
TITLE = "Market Share",
CATEGORIES = ["A", "B", "C"],
SERIES = [{NAME = "Share", DATA = [38.5, 22.3, 15.7]}]
}
]]>
</excel>
</excels>
package com.example;
import com.github.paohaijiao.xml.param.Param;
public interface ChartExportService {
void export(@Param("field") String field, @Param("value") String value);
}
package com.example;
import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.util.JObjectConverter;
import com.github.paohaijiao.xml.factory.JQuickFactory;
import com.github.paohaijiao.xml.factory.JQuickXmlFactory;
import com.github.paohaijiao.xml.handler.JQuickParseHandler;
import com.github.paohaijiao.xml.ex.JQuickExcelExportXmlParseFactory;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class ChartExportDemo {
public static void main(String[] args) throws Exception {
List<Map<String, Object>> data = new ArrayList<>();
List<JQuickRow> rows = JQuickRow.toRows(JObjectConverter.convert(data));
try (OutputStream output = new FileOutputStream("chart.xlsx")) {
JQuickParseHandler parser = new JQuickExcelExportXmlParseFactory(rows, output);
JQuickFactory factory = new JQuickXmlFactory(parser, "jquick-excel.xml");
ChartExportService service = factory.createApi(ChartExportService.class);
service.export("field", "value");
}
}
}

Export to an HTTP response (browser download, Spring Boot)

Section titled “Export to an HTTP response (browser download, Spring Boot)”

The framework does not depend on Spring, so in Spring Boot simply use the response stream (HttpServletResponse.getOutputStream()) as the export target:

package com.example.controller;
import com.example.WebExportService;
import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.util.JObjectConverter;
import com.github.paohaijiao.xml.factory.JQuickFactory;
import com.github.paohaijiao.xml.factory.JQuickXmlFactory;
import com.github.paohaijiao.xml.handler.JQuickParseHandler;
import com.github.paohaijiao.xml.ex.JQuickExcelExportXmlParseFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@RestController
public class StudentExportController {
@GetMapping("/students/export")
public void exportStudents(HttpServletResponse response) throws Exception {
// 业务数据
List<Map<String, Object>> data = new ArrayList<>();
List<JQuickRow> rows = JQuickRow.toRows(JObjectConverter.convert(data));
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("UTF-8");
String fileName = URLEncoder.encode("学生表.xlsx", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-Disposition",
"attachment; filename*=UTF-8''" + fileName);
try (OutputStream output = response.getOutputStream()) {
JQuickParseHandler parser = new JQuickExcelExportXmlParseFactory(rows, output);
JQuickFactory factory = new JQuickXmlFactory(parser, "jquick-excel.xml");
WebExportService service = factory.createApi(WebExportService.class);
service.export("field", "value");
output.flush();
}
}
}
package com.example.controller;
import com.github.paohaijiao.xml.param.Param;
public interface WebExportService {
void export(@Param("field") String field, @Param("value") String value);
}

Export to a local file / write to an OutputStream

Section titled “Export to a local file / write to an OutputStream”

Export data can be written to any OutputStream: use FileOutputStream for local files and the corresponding target’s output stream in other scenarios. See the “Minimal export example” earlier in this document for a complete example; the core code is as follows:

import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.util.JObjectConverter;
import com.github.paohaijiao.xml.handler.JQuickParseHandler;
import com.github.paohaijiao.xml.ex.JQuickExcelExportXmlParseFactory;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.Map;
public class OutputTargetDemo {
// 写入本地文件
public void writeToFile(List<Map<String, Object>> data, String filePath) throws Exception {
List<JQuickRow> rows = JQuickRow.toRows(JObjectConverter.convert(data));
try (OutputStream output = new FileOutputStream(filePath)) {
JQuickParseHandler parser = new JQuickExcelExportXmlParseFactory(rows, output);
buildAndExport(parser);
}
}
// 写入内存输出流(例如后续作为邮件附件、对象存储内容)
public byte[] writeToMemory(List<Map<String, Object>> data) throws Exception {
List<JQuickRow> rows = JQuickRow.toRows(JObjectConverter.convert(data));
try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
JQuickParseHandler parser = new JQuickExcelExportXmlParseFactory(rows, output);
buildAndExport(parser);
return output.toByteArray();
}
}
private void buildAndExport(JQuickParseHandler parser) {
// 由 XML 工厂创建代理并完成导出,此处省略服务接口调用
}
}

For high-row-count exports, streaming behavior can be tuned through JQuickExcelConfig. The default configuration is as follows:

Option Purpose Default
streamingExportEnabled Whether switching to SXSSF streaming writes is allowed true
streamingRowAccessWindowSize Number of rows kept in memory 100
streamingExportThreshold Automatically switch to streaming when this row count is reached 5000
streamingCompressTempFiles Whether to compress streaming temporary files true
cellStyleCacheEnabled Whether cell style caching is enabled (avoids exceeding the POI 64000 style limit) true
import com.github.paohaijiao.config.JQuickExcelConfig;
public class ConfigDemo {
public static void configure() {
JQuickExcelConfig config = JQuickExcelConfig.getInstance();
config.setStreamingExportEnabled(true)
.setStreamingRowAccessWindowSize(100)
.setStreamingExportThreshold(5000)
.setStreamingCompressTempFiles(true)
.setCellStyleCacheEnabled(true);
}
}

The import parse factory takes an InputStream as input. When reading from a file path, construct a file input stream first:

import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.xml.factory.JQuickFactory;
import com.github.paohaijiao.xml.factory.JQuickXmlFactory;
import com.github.paohaijiao.xml.handler.JQuickParseHandler;
import com.github.paohaijiao.xml.im.JQuickExcelImportXmlParseFactory;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.List;
public class FilePathImportDemo {
public static void main(String[] args) throws Exception {
try (InputStream input = new FileInputStream("D:/data/students.xlsx")) {
JQuickParseHandler parser = new JQuickExcelImportXmlParseFactory(input);
JQuickFactory factory = new JQuickXmlFactory(parser, "jquick-excel.xml");
FilePathImportService service = factory.createApi(FilePathImportService.class);
List<JQuickRow> rows = service.importExcel("field", "value");
System.out.println("导入行数:" + rows.size());
}
}
}

JQuickExcelImportXmlParseFactory directly accepts an InputStream from any source, such as a classpath resource, a network stream or an uploaded file stream:

import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.xml.factory.JQuickFactory;
import com.github.paohaijiao.xml.factory.JQuickXmlFactory;
import com.github.paohaijiao.xml.handler.JQuickParseHandler;
import com.github.paohaijiao.xml.im.JQuickExcelImportXmlParseFactory;
import java.io.InputStream;
import java.util.List;
public class StreamImportDemo {
public List<JQuickRow> doImport(InputStream input) throws Exception {
JQuickParseHandler parser = new JQuickExcelImportXmlParseFactory(input);
JQuickFactory factory = new JQuickXmlFactory(parser, "jquick-excel.xml");
StreamImportService service = factory.createApi(StreamImportService.class);
return service.importExcel("field", "value");
}
}

For large-file imports, the batch reading capability of JExcelImportHandler is recommended: consume in fixed batch sizes to avoid holding all rows at once:

import com.github.paohaijiao.handler.JExcelImportHandler;
import com.github.paohaijiao.model.JExcelImportModel;
import com.github.paohaijiao.statement.JQuickRow;
import java.io.InputStream;
import java.util.List;
public class BatchImportDemo {
public static void main(String[] args) throws Exception {
JExcelImportModel model = new JExcelImportModel();
try (InputStream input = new java.io.FileInputStream("D:/data/big.xlsx")) {
JExcelImportHandler handler = new JExcelImportHandler(input);
// 每 5000 行消费一批;返回值为实际读取的总行数
int total = handler.importDataInBatch(model, 5000, (List<JQuickRow> batch) -> {
process(batch);
// 返回 true 继续读取下一批
return true;
});
System.out.println("总计读取:" + total);
}
}
private static void process(List<JQuickRow> batch) {
// 在此处理当前批次数据
}
}

Import-related default settings in JQuickExcelConfig:

Option Purpose Default
bigFileImportEnabled Whether OPCPackage-based parsing is enabled to reduce peak memory true
importBatchThreshold Recommended row-count threshold for batch processing (advisory for consumers, not enforced) 20000

When exporting to a local file, use FileOutputStream as the output target. See “Minimal export example” for a complete example. Core usage:

import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.xml.handler.JQuickParseHandler;
import com.github.paohaijiao.xml.ex.JQuickExcelExportXmlParseFactory;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.List;
public class WriteFileDemo {
public void write(List<JQuickRow> rows, String filePath) throws Exception {
try (OutputStream output = new FileOutputStream(filePath)) {
JQuickParseHandler parser = new JQuickExcelExportXmlParseFactory(rows, output);
// 使用 JQuickXmlFactory 创建代理并调用导出方法
}
}
}

The export target can be any OutputStream: an HTTP response stream, ByteArrayOutputStream, an object-storage upload stream, etc. The stream passed into the factory is closed by the caller; try-with-resources is recommended:

import com.github.paohaijiao.statement.JQuickRow;
import com.github.paohaijiao.xml.handler.JQuickParseHandler;
import com.github.paohaijiao.xml.ex.JQuickExcelExportXmlParseFactory;
import java.io.OutputStream;
import java.util.List;
public class WriteStreamDemo {
public void write(List<JQuickRow> rows, OutputStream target) {
// JAssert 要求 rows 与 OutputStream 均不能为 null
JQuickParseHandler parser = new JQuickExcelExportXmlParseFactory(rows, target);
// 使用 JQuickXmlFactory 创建代理并调用导出方法
}
}

The README does not provide an item-by-item comparison table against EasyExcel or raw POI. The following are only facts confirmable at the code level:

  • JQuick-Excel is a wrapper on top of Apache POI; it does not change POI’s workbook model, and both normal writes and SXSSF streaming writes are provided by POI.
  • JQuick-Excel’s main addition is XML service definitions + declarative DSL: worksheet selection, header mapping, value conversion, validation, formulas, styling, merging, charts and footers are all described centrally as parameters.
  • The framework does not provide a listener mode; large-file imports are consumed through batch reading.
  • The framework does not mandate Spring; service proxies created through the factory can be used in any Java program.

Beyond the facts above, the README currently provides no item-by-item capability comparison.

1. Where should the XML definition file be placed?

On the classpath (the resources directory). Pass the file name as a parameter when creating JQuickXmlFactory, for example new JQuickXmlFactory(parser, "jquick-excel.xml").

2. Can the keywords and punctuation in the DSL be rewritten?

No. Keywords such as IMPORT, EXPORT and WITH, as well as braces, colons, commas and the range syntax, must be preserved exactly; otherwise parsing will fail.

3. How do I avoid excessive memory usage during high-row-count exports?

By default JQuickExcelConfig allows automatic switching to SXSSF streaming writes when the data volume reaches 5000 rows, keeping 100 rows in memory by default; adjust as needed.

4. How do I import a large file?

Use JExcelImportHandler.importDataInBatch(model, pageSize, consumer) to consume in batches; the recommended batch threshold in the configuration defaults to 20000 rows.

5. Why is JContext needed?

External values referenced as ${key} in TRANSFORM expressions (data dictionaries, for example) are provided through JContext; ${field} reads the current row’s field values.

6. Is there a limit on the number of cell styles?

POI imposes a cell style count limit. The framework enables style caching by default to reuse styles; keeping cellStyleCacheEnabled=true is recommended.

7. What is the difference between TRANSFORM functions and FORMULAS formulas?

TRANSFORM functions are Java expressions evaluated directly during import/export; FORMULAS writes Excel formulas calculated when the file is opened by spreadsheet software. Even identically named ones are not interchangeable.

8. What should I watch out for with chart data?

Each series’ DATA array should correspond one-to-one with CATEGORIES (aligned lengths); otherwise chart categories and data will be misaligned.

9. Do streams need to be closed manually?

Yes. Input streams and output streams are provided and closed by the caller; try-with-resources is recommended.

10. How do I use the built-in export themes?

You can pass a theme code when creating the export parse factory (for example new JQuickExcelExportXmlParseFactory("jade", context, rows, output)); the framework ships 42 built-in themes. When no theme is passed, the default style is used.

The following are limitations confirmable from the code and README:

  • The current version does not provide a listener mode and does not provide global converter registration; value conversion is accomplished through the DSL TRANSFORM parameter plus the optional SPI function dependency.
  • Proxy mode relies on the XML definition file on the classpath; the XML <excel name> must match the service interface method name, and namespace must match the service interface’s fully qualified name.
  • When importing through the XML proxy, if an IO exception occurs during execution, the exception is caught and logged, and the method ultimately returns an empty collection.
  • The 248 TRANSFORM functions provided by jquick-transform-function are available only after that dependency is explicitly introduced.
  • Worksheet indexes use 1-based counting; when a target worksheet specified by name does not exist, it is created automatically.
  • Only the xls and xlsx workbook formats are supported.

JQuick-Excel is released under the Apache License, Version 2.0.

Copyright (c) [2025-2099] Martin ([email protected])
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Each group is paginated ten demos per batch — use “Previous” / “Next” to page through.