# Kandy: a plotting DSL for Kotlin that picks your rendering engine

> A JetBrains incubated Kotlin library that gives you one DSL for building charts and lets you choose Lets-Plot, Apache ECharts or a geo module underneath it. It integrates with Kotlin DataFrame and runs in IntelliJ notebooks, Datalore and Jupyter, and it is still marked experimental.

**Kotlin/kandy** — Kotlin plotting library.

- Repository: https://github.com/Kotlin/kandy
- Website: https://kotlin.github.io/kandy/
- Stars: 746 · Forks: 28
- Language: Kotlin
- License: Apache-2.0
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/kotlin-kandy

## One DSL, several engines, and a choice you make per module

Kandy is an open-source plotting library for Kotlin that provides a powerful and flexible DSL for creating graphs and utilizes various popular engines.

The library aims to make it easy and quick for users to create both basic and complex charts with many parameters and settings, without the need for lengthy documentation. And depending on the task, users can choose from different engines for the perfect visualization.

The module list shows how that choice is structured. kandy-api provides a simple-to-use API for creating charts, and it is the part that defines the DSL. kandy-lets-plot offers an implementation of the Lets-Plot library, which is based on Leland Wilkinson's work The Grammar of Graphics and is a proven tool for creating visualizations. kandy-echarts provides an implementation of the Apache ECharts library, which is a widely used tool for creating interactive visualizations.

The tree also carries kandy-geo, which the readme does not describe in the module list but which is clearly a separate artifact in the repository.

So the architecture is a shared front end over swappable back ends. That is the whole design idea, and it has an important consequence: the DSL is only the part that is genuinely Kandy. Everything about how a chart actually gets drawn, how it scales, how it exports, whether it is interactive, comes from Lets-Plot or ECharts. Which is the strength of the approach and also its main constraint, and it is worth deciding early which backend you will live with.

The project says it is inspired by The Grammar of Graphics, which explains the Lets-Plot affinity and the vocabulary of layers, scales and layouts in the API.

## The quickstart is a plot block with layers inside it

The first example in the readme is the whole API in five lines. Inside a Kotlin Notebook, Datalore or Jupyter with the Kotlin Kernel, you use the kandy magic and write a plot block:

```kotlin
%use kandy

plot {
  bars { 
    x(listOf("first", "second", "third"))
    y(listOf(7, 3, 5))
  }
}
```

The structure is a plot block containing layer blocks. A bars block is a bar layer, and inside it you set the x and y values. The same API shape is used for other layer types, so a line or a scatter layer sits in the plot block the same way bars does here.

When you have data in a map, you pass the map to plot and reference columns by name:

```kotlin
plot(weatherData) { // Begin plotting
    x("time") // Set x-axis with time data
    y("temperature") { // Set y-axis with temperature data
        // Define scale for temperature (y-axis)
        scale = continuous(0.0..25.5)
    }
}
```

The scale is set inside the y block, which means the axis configuration lives with the data binding rather than in a separate block. That is the idiom to notice: the DSL is a nested block structure where each block owns its own settings, and it is why the code reads top to bottom as a description of the chart.

The readme points to the project documentation for guidance on getting started, additional examples and tutorials, and every example in it is available in three forms. You can get the Lets-Plot example as a notebook, as a Datalore notebook, or as a Kotlin project, which tells you the library is meant to be used in all three environments rather than only in one.

## Lets-Plot and ECharts differ in what they actually give you

The two backends are not interchangeable, and the readme is honest that you choose depending on the task.

The Lets-Plot implementation gives high-speed chart rendering and the ability to work with large amounts of data without delays or waiting, which is its selling point inside the Kotlin Notebook plugin. The rendering is Swing based for outputs in the Kotlin Notebook plugin, and the library also supports export to various formats.

The ECharts implementation is the interactive one. The feature list calls out interactive chart features including interactive tooltips for charts rendered in HTML and Swing, and ECharts is described as a widely used tool for creating interactive visualizations.

The ECharts example in the readme shows the difference in the API. The data is a map of four columns, and the axis binding uses an explicit type parameter, x("days_of_week"<String>()), because the column type has to be resolved for the engine. The bar layer sets a name, a y binding with a scale, a colour binding with its own scale from grey to blue, and a label block with a position and a formatter:

```kotlin
    bars {
        name = "Precipitation"
        y("precipitation"<Double>()) { scale = continuous(0.0..200.0) }
        color("temp"<Double>()) { scale = continuous(range = Color.GREY..Color.BLUE) }
        label {
            position = LabelPosition.TOP
            formatter = "{@precipitation} ml"
        }
    }
```

The formatter string uses a template reference to the precipitation column. The layer also carries its own legend entry through the name property.

One thing to note about the readme is that it presents Kandy as working with minimal fuss inside Kotlin notebooks on various platforms. The library itself has friction, but the notebook integration is the smoothest path in, and that is where you should start evaluating it.

## Kotlin DataFrame is the reason to be here

The integration with Kotlin DataFrame is what separates Kandy from a plotting library you could have written in any language.

Kandy integrates with Kotlin DataFrame, another Kotlin library for working with data, allowing data processing to flow directly into the final visualization. The feature list describes the benefit as Kotlin collections and DataFrame support, with graph construction using Kotlin DataFrame, especially suitable for hierarchical dataframes.

That last clause is the interesting one. Hierarchical dataframes are a shape that many plotting libraries handle awkwardly, because a grouped dataset is not naturally a flat table of x and y columns. If your data is grouped, and it needs to stay grouped through the visualization, then a library that takes the same dataframe type all the way to the chart is doing something for you.

The type and null safety claim is the other half of the Kotlin story. Type safety and Kotlin null safety are listed as features, and in a DSL where you are binding string column names to typed data, the type parameter on the binding, as in y("precipitation"<Double>()), is what makes a mismatch a compile error rather than an empty chart.

The practical consequence is that a Kandy chart is a value in the same type system as the rest of your Kotlin pipeline. There is no boundary where data becomes a map of lists and the chart is built from loose values, which is where the type safety actually pays off.

## Export formats are engine capabilities, not Kandy features

The multiple format export feature allows charts to be saved in various formats like svg, png, html, jpg/jpeg, and tiff.

It is worth being precise about where that list comes from, because it is the kind of thing that gets assumed rather than checked. The formats are produced by the engine underneath. Lets-Plot has its own export story, ECharts has its own, and the Swing rendering path has its own. The formats Kandy can save depend on the backend module you added and on what that engine supports in the environment you are running in.

This is not a criticism of the design. A shared DSL over multiple engines necessarily has a shared API whose capabilities are the intersection of what the engines offer, and where they differ you get engine-specific behaviour. The readme even notes that some of the export formats require additional support libraries, in the context of the export format list.

The practical advice is to check the export path against the engine you plan to use rather than against the feature list. If you need a specific format for a report or a paper, confirm it with the backend module before you build your pipeline around it.

The other capability that follows from the engine choice is interactivity. Interactive tooltips for charts rendered in HTML and Swing are listed as a feature, and that is available where the backend supports it, which in practice means the ECharts path is the one to investigate if hover behaviour matters to you.

## The badges say experimental, and that is the honest headline

The first thing in the readme is a badge for Experimental, pointing at the Kotlin documentation on component stability. The second is a badge for a JetBrains incubator project. Both are there for a reason and both should shape your expectations.

The experimental designation means the API can change. A DSL like this one is exactly the kind of surface that shifts while it finds its shape, because the design is where most of the opinion lives. If you write a large body of chart code against Kandy today, you should expect to revisit it when the API stabilises.

The incubator designation is a different signal. It means the project is hosted and developed by JetBrains under its incubator programme, which is a statement about sponsorship and attention rather than about production readiness. The library is on Maven Central under org.jetbrains.kotlinx, with the kandy-api module published there, so it is a real dependency you can depend on.

The release history is consistent with that reading. The recent releases are v0.8.3 on 2025-12-12, v0.8.0 on 2025-02-13, and 0.7.1 on 2024-09-27. So the project is pre-1.0, on a 0.x version line, and shipping every few months.

The repository is not archived and the last push was on 2026-10-15, so the code is being worked on. The licence is Apache-2.0, with the LICENSE file at the root of the repository and the badge pointing at the Apache 2.0 text.

One more structural detail worth knowing if you contribute. The tree is a Gradle project with a settings.gradle.kts, a build.gradle.kts, the gradle wrapper including gradlew and gradlew.bat, a gradle.properties, and separate module directories for each artefact, plus a util directory and an examples directory holding notebooks, IntelliJ examples and images.

## Where a Kotlin plotting library is the wrong choice

There are clear cases where Kandy is the wrong answer, and they are about context rather than about chart quality.

The first is a Python or R data stack. If your data work lives in pandas, matplotlib or plotnine, or in R with ggplot2, then Kandy asks you to leave that ecosystem and rebuild your plotting in Kotlin. The chart quality is comparable and the workflow cost is not. Kandy is a strong choice when the surrounding code is Kotlin, and a poor choice when it is a plotting island in a Python pipeline.

The second is production maturity. An experimental badge and a 0.x version mean you are adopting an API that may change and a project that does not promise compatibility. For a notebook that a data scientist runs interactively, that is fine, and honestly it is the intended setting. For a reporting service that generates charts on a schedule for customers, it is a different risk calculation.

The third is the engine indirection. When something goes wrong with a chart, you now have three places to look: your Kandy code, the engine, and the layer of translation between them. If you need to drop down to the engine's own API for a specific feature, you lose the uniformity that motivated choosing Kandy in the first place. If you are the kind of user who needs one specific exotic chart type that only one engine supports, you may end up with two rendering paths.

The fourth is the group size and the geometry. The tool is described as lightweight and the code is idiomatic Kotlin, but the way it works is that you describe a chart rather than draw one. If your charts need per-element annotation, hand tuned geometry, or a layout you have to iterate on visually, a declarative DSL is a poor fit and an imperative plotting API will be faster.

And the fifth is simply that the engine you would use directly may already be good enough. Lets-Plot and ECharts are both usable on their own. Kandy earns its place by giving you one Kotlin-shaped API over them and by fitting into a Kotlin DataFrame pipeline, not by rendering better than they do.

## Against calling the engine directly, and against the Python plotting stack

Two comparisons settle the decision.

The first is against using Lets-Plot or ECharts directly. The difference in approach is the layer you write your chart in. Calling the engine directly means you are inside its API, its configuration model and its version. Kandy sits above that and gives you a Kotlin DSL with layers, scales, layout and legends, expressed as nested blocks with typed column bindings. The benefit is that your chart code looks like Kotlin and composes with your data code. The cost is the abstraction tax and the fact that the escape hatch, when you need it, drops you back into the engine.

So the honest framing is that Kandy is not a rendering engine. It is a Kotlin-shaped front end for engines that already exist, and it adds the DataFrame integration and the notebook experience on top. If you are happy in the engine's own idiom, Kandy is extra machinery. If you want the engine's capability from Kotlin, it is the right layer.

The second is against the Python plotting ecosystem. Python has matplotlib, plotnine, seaborn and Altair, and the Jupyter notebook workflow for data analysis is centred there. Kandy's pitch is that you can do the same kind of work in Kotlin, in the Kotlin Notebook plugin, in Datalore, and in Jupyter with the Kotlin Kernel, and get the same result with type safe column bindings.

That is a real advantage if your team is a Kotlin team, especially if you already use Kotlin DataFrame and want one language across ingestion, transformation and visualization. It is not an argument for moving a Python team to Kotlin. Choose Kandy when your pipeline is already Kotlin and the plotting is the last mile. Otherwise the well-trodden path is likely faster to a result.

## Conclusion

Adopt Kandy if your team already writes Kotlin and Kotlin DataFrame, and you want charts from the same pipeline as the rest of your data work without leaving the language. Skip it if you need a stable production plotting stack, because the project is badged as an experimental component and a JetBrains incubator project, and it skips it if your charts must live in a Python or R pipeline. Verify first which backend module matches your rendering needs, since the DSL surface is shared but the engines differ, and check that the export formats you need are produced by the engine you choose rather than by Kandy itself.

## FAQ

### What is Kandy and what does it do?

Kandy is an open-source plotting library for Kotlin that provides a flexible and idiomatic DSL for creating charts while letting you pick the rendering engine. The kandy-api module defines the API, kandy-lets-plot implements Lets-Plot and kandy-echarts implements Apache ECharts.

### How do I create a chart with Kandy in a notebook?

Use the kandy magic and write a plot block containing a layer block. The readme example uses %use kandy followed by plot { bars { x(listOf("first", "second", "third")); y(listOf(7, 3, 5)) } }, which works in the Kotlin Notebook plugin, in Datalore, and in Jupyter with the Kotlin Kernel.

### Which rendering engines does Kandy support?

kandy-lets-plot provides an implementation of Lets-Plot, based on the Grammar of Graphics, and is described as enabling high-speed chart rendering with large amounts of data without delays. kandy-echarts provides an implementation of Apache ECharts, aimed at interactive visualizations.

### How does Kandy work with Kotlin DataFrame?

Kandy integrates with Kotlin DataFrame, another Kotlin library for working with data, allowing data processing to flow directly into a visualisation from data processing to final visualization, and it is especially suitable for hierarchical dataframes. Column bindings are typed, so a type mismatch becomes a compile error rather than an empty chart.

### Which file formats can Kandy export a chart to?

The feature list names svg, png, html, jpg and jpeg, and tiff. These come from the underlying engine rather than from Kandy itself, so which formats are available depends on the backend module you use and the environment it runs in.

### Is Kandy ready for production use?

The project is badged as experimental and as a JetBrains incubator project, and its recent releases are v0.8.3 from 2025-12-12, v0.8.0 from 2025-02-13 and 0.7.1 from 2024-09-27, so it is pre-1.0. It is licensed Apache-2.0, the repository is not archived and the last push was on 2026-10-15.

## Sources

- [Kotlin/kandy on GitHub](https://github.com/Kotlin/kandy)
- [License: Apache-2.0](https://github.com/Kotlin/kandy/blob/main/LICENSE)
- [Project website](https://kotlin.github.io/kandy/)
- [README](https://github.com/Kotlin/kandy/blob/main/README.md)
- [Releases](https://github.com/Kotlin/kandy/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/kotlin-kandy
