DocxKtm
September 28, 2025 ยท View on GitHub
DocxKtm is a powerful and intuitive Kotlin DSL (Domain-Specific Language) for creating and manipulating Microsoft Word .docx files. Built as a wrapper around the robust docx4j Java library, DocxKtm simplifies document generation by providing a clean, readable, and expressive API, allowing you to define complex documents with idiomatic Kotlin code.
Whether you need to generate reports, invoices, or any other structured document, DocxKtm streamlines the process, freeing you from the verbosity of the underlying XML and Java APIs.
Features
- Fluent Kotlin DSL: A declarative, easy-to-read API for building documents.
- Document Creation & Editing: Create new documents from scratch or open and modify existing ones.
- Rich Content: Easily add and style paragraphs, text, tables, images, headers, and footers.
- Powerful Templating:
- Simple key-value placeholder replacement (
${variable}) provided bydocx4j. - Advanced type-safe templating for classes, numbers, dates, and currencies with custom formatting based on
MVEL2evaluations. - Rich
MVEL2syntax support for complex expressions, conditional logic, and loops within templates. - Dynamic table population from collections of objects or JSON arrays.
- Simple key-value placeholder replacement (
- Comprehensive Styling: Apply styles to text, paragraphs, tables, rows, and cells.
- Full
docx4jAccess: Drop down to the underlyingdocx4jAPI for advanced or unsupported features.
Installation
Add the library to your project's dependencies.
Gradle (Kotlin DSL)
// build.gradle.kts
repositories {
mavenCentral()
}
dependencies {
implementation("io.github.alexmaryin:docxktm:1.3.2") // Replace with the latest version
}
Maven
<!-- pom.xml -->
<dependency>
<groupId>io.github.alexmaryin</groupId>
<artifactId>docxktm</artifactId>
<version>1.3.2</version> <!-- Replace with the latest version -->
</dependency>
Getting Started
Creating a New Document
Use the DocxNew function to create a new document from scratch. The body block is the entry point for adding content.
fun main() {
DocxNew("output/hello_world.docx") {
body {
paragraph {
text("Hello, DocxKtm!")
}
}
}
}
Opening and Modifying an Existing Document
Use DocxOpen to load an existing .docx file, modify it, and save the changes to a new file (or overwrite the original).
fun main() {
DocxOpen("input.docx", "modified.docx") {
body {
paragraph(ParagraphStyle(styleName = Styles.TITLE)) {
text("This is a new title added to the document.")
}
}
}
}
DSL Reference
Paragraphs and Text
The core of any document is its text. DocxKtm provides a simple way to add paragraphs and styled text runs.
// ... inside body { ... }
paragraph(ParagraphStyle(styleName = Styles.TITLE, alignment = Alignment.CENTER)) {
text("Main Title")
}
paragraph(ParagraphStyle(spacing = ParagraphSpacing(before = 12, after = 24))) {
text("This is the first run of text. ")
text(
"This is a second run, but bold and blue.",
style = TextStyle(bold = true, color = WordColor.Blue)
)
}
paragraph {
text("This is the first line.", breakLine = true)
text("This is the second line in the same paragraph.")
}
Tables
Create complex tables with full control over styling for the table, rows, and cells.
// ... inside body { ... }
table(
TableStyle(
width = TableWidth.PageFit,
borders = TableBorder.All(BorderStyle(type = TableBorderType.SINGLE, width = 1))
)
) {
// Header Row
row(RowStyle(headerRow = true, height = RowHeight.AtLeast(20))) {
cell(CellStyle(width = TableWidth.Percent(30))) {
paragraph { text("Header 1", style = headerRowStyle) }
}
cell(CellStyle(width = TableWidth.Percent(70))) {
paragraph { text("Header 2", style = headerRowStyle) }
}
}
// Data Row
row {
textInCell("Data 1.1")
textInCell("Data 1.2")
}
// Data row with simplified syntax for cells
row {
listInCells(List(3) { "Data 2.${it + 1}" })
}
// Row with spanned cells
row {
cell(CellStyle(spannedCells = 2, verticalAlign = VerticalAlign.CENTER)) {
paragraph(ParagraphStyle(alignment = Alignment.CENTER)) {
text("This cell spans two columns.")
}
}
}
}
Images
Embed images from files and control their size and placement.
// ... inside body { ... }
paragraph(ParagraphStyle(alignment = Alignment.CENTER)) {
// Bind the image first to make it available to the document
val image = bindImage(
filename = "path/to/your/image.png",
description = "My awesome image"
)
imageFromFile(image)
}
paragraph {
// You can also specify a size (in EMU - English Metric Units)
val sizedImage = bindImage(
filename = "path/to/your/image.png",
width = 5.fromCmToEMU(), // Set width to 5cm, height will be scaled
height = 3.fromCmToEMU() // Or set both width and height
)
imageFromFile(sizedImage)
}
Headers and Footers
Add headers and footers to your document, including dynamic page numbers.
// ... inside DocxNew { ... }
header(ParagraphStyle(alignment = Alignment.CENTER)) {
text("My Document Header")
}
body {
// ... main document content
}
footer(ParagraphStyle(alignment = Alignment.RIGHT)) {
text("Page ")
// #p is replaced by the current page number
// #t is replaced by the total number of pages
pageNumber("page #p of #t")
}
Simple Templating
DocxKtm supports simple templating with placeholders in the format ${placeholder}
This feature uses the fastest algorithm provided by docx4j engine. To use it just write inside the body block of the opened document:
// ... inside body { ... }
val replacementsMap = mapOf("name" to "Alex", "age" to "41")
mergeTemplateStrMap(replacementsMap)
Remark
However, my own experiments convincingly demonstrate that MVEL2 evaluations,
even in simple cases of using a dictionary of strings to replace simple substitution fields,
are twice as fast as the docx4j algorithm on reasonable dictionary examples
(hmm, let's say 500, if you have a document with that many substitution fields, let me know)
and are still faster on the dictionary the size of 5000 fields.
See for details tests in src/test/kotlin/templates/TemplatesPerformanceTests.kt
Advanced templating
DocxKtm ships with a powerful, type-safe templating engine.
Design your .docx file once, drop ${placeholders} anywhere (header, footer, table cell, text-box, etc.) and populate them at runtime with a single Kotlin DSL block.
1. Quick start
DocxTemplate(
templateFilename = "template.docx",
outputFilename = "output.docx",
filler = "n/a" // default = empty string
) {
"name" to "John Doe"
}
2. Supported value types
| Type | Example | Notes |
|---|---|---|
| Text | "title" to "Invoice #12" | |
| Numbers | "qty" to 42 | |
| Custom number format | "total" to 12345.67 with NumberFormat("0#,###.00") | Any DecimalFormat pattern |
| Currency | "price" to 99.99 with CurrencyFormat.USD | Built-in: USD, EUR, RUB, CHF |
| Date | "shipDate" to LocalDate.now() with DateFormat("dd.MM.yyyy") | |
| Date-time | "created" to LocalDateTime.now() with DateFormat("dd.MM.yyyy HH:mm") |
3. Bulk replacements
3.1 From a Map<String,Any>
fromMap(
mapOf(
"customer" to "Alice",
"age" to 30,
"items" to listOf(1, 2, 3)
)
)
3.2 From a data class
data class User(val name: String, val age: Int, val gender: Gender)
val user = User("Alex", 41, Gender.MALE)
"user" to user // template: ${user.name}, ${user.age}, ${user.gender.name()}
3.3 From JSON (String or JsonElement)
val json = """
{
"order": {
"id": 123,
"customer": "Me",
"items": [
{ "name": "Keyboard", "price": 119.99 },
{ "name": "Mouse", "price": 49.99 }
]
}
}
"""
fromJsonString(json) // or fromJson(Json.parseToJsonElement(json))
4. Mix & match
All builders can live together in the same block:
DocxTemplate("tpl.docx", "out.docx") {
"reportTitle" to "Q2 Sales"
"total" to 9876.5 with CurrencyFormat.EUR
fromMap(mapOf("region" to "EMEA", "manager" to "Bo"))
fromJsonString("""{"note":"Approved by CFO"}""")
}
5. Tips for your template.docx
- Use
${simpleKey}for top-level values. - Use
${object.field}for nested properties (map keys or data-class members). - For enums write
${user.gender.name()}. - Placeholders inside tables, headers, footers, and text-boxes are all supported.
- Missing keys render as the
fillerstring (default: empty).
MVEL2 Expression Language
Take full control of your templates with the MVEL2 expression language. This allows for complex logic, transformations, and data manipulation directly within your .docx template.
Template file mvel_template.docx:
> Customer: ${customer.name.toUpperCase()}
>
> @if{customer.orders != empty}
> Recent Orders:
> @foreach{order : customer.orders}
> - Order #${order.id} - Amount: ${order.amount}
> @end{}
> @else{}
> No recent orders.
> @end{}
Kotlin code:
data class Customer(val name: String, val orders: List<Order>)
data class Order(val id: Int, val amount: Double)
fun generateMvelReport() {
val customer = Customer(
name = "John Doe",
orders = listOf(Order(1, 120.50), Order(2, 75.00))
)
DocxTemplate("mvel_template.docx", "mvel_report.docx") {
"customer" to customer
}
}
Dynamic Table Population
Populate tables dynamically from a list of objects or a JSON string. DocxKtm automatically creates new rows for each item in the collection.
From a Collection of Objects
Define a table with a single data row and use the @foreach {item: collection_name} syntax in the first cell of that row.
Template file table_template.docx:
| Product Name | Quantity | Price |
|---|---|---|
@foreach{product : products} ${product.name} | ${product.quantity} | ${product.price} |
Kotlin code:
data class Product(val name: String, val quantity: Int, val price: Double)
fun generateProductTable() {
val products = listOf(
Product("Laptop", 1, 1200.00),
Product("Mouse", 2, 25.50),
Product("Keyboard", 1, 75.00)
)
DocxTemplate("table_template.docx", "product_table.docx") {
"products" to products
}
}
Examples
You may find much more detailed samples for using DocxKtm in tests sources src/test/kotlin
Contributing
Contributions are welcome! Please feel free to submit a pull request or create an issue for bugs, questions, or feature requests.
- Fork the repository.
- Create a new feature branch (
git checkout -b feature/your-feature). - Commit your changes (
git commit -am 'Add some feature'). - Push to the branch (
git push origin feature/your-feature). - Create a new Pull Request.
License
Copyright 2025 Alex Maryin
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.