Pkl 0.28 Release Notes
Pkl 0.28 was released on February 26th, 2025.
The latest bugfix release is 0.28.0. (All Versions)
The next release (0.29) is scheduled for June 2025. To see what’s coming in the future, follow the Pkl Roadmap.
Please send feedback and questions to GitHub Discussions, or submit an issue on GitHub.
Pkl is hosted on GitHub. To get started, follow Installation.
Highlights 💖
News you don’t want to miss.
New parser
The first step to evaluating a program is to parse its source code into a parse tree. Currently, Pkl uses ANTLR to generate parser code from grammar files.
Using this approach has allowed for rapidly iterating and changing the language’s grammar. However, Pkl’s grammar has matured; it’s not as likely that the grammar will change from one version to the next. Additionally, the current parser’s performance has become more painful as projects written in Pkl grow in size. In many cases, parsing can take up most of the time spent during evaluation.
In 0.28, the ANTLR-generated parser is being replaced by a handwritten parser. In benchmarking tests, this has shown to improve parsing speed by around two orders of magnitude. Here are some quick comparisons between the two parsers, run on a MacBook Pro M4 Max machine, with 5 warmup iterations, and run for 10 iterations:
Old parser | New parser | |
---|---|---|
440.8ms |
5.9ms |
|
136.7ms[1] |
0.8ms |
|
All pkl-pantry modules |
1424.2ms |
7.0ms |
Noteworthy 🎶
Ready when you need them.
CLI improvements
Colored help messages
The pkl
, pkldoc
, pkl-codegen-java
, and pkl-codegen-kotlin
CLIs now emits help text with colors (#947).
Here is a sneak peek:

Thanks to @gordonbondon for their contribution!
jpkl
support on Windows
The jpkl
executable is now supported on Windows (#872).
jpkl
is a fat jar that can be executed directly when on macOS and Linux, but current users of Windows are required to execute it with java -jar <path/to/jpkl>
.
In 0.28, Windows users can also call execute this command directly.
To do so, the filename should be called jpkl.bat
, and placed in PATH
.
Type constraint changes
Type annotations that are executed as a result of calling a constraint expressions now perform an eager typecheck (#964).
In Pkl 0.27, we changed how typechecks are executed for Listing
and Mapping
types.
However, this had two unintended side effects:
For one, error messages from certain failing constraints show ?
in place of values that are failing.
The following snippet:
class Bird { name: String }
bird: Listing<Bird>(firstOneIsQuail) = new {
new { name = "Pigeon" }
new { name = "Quail" }
}
local firstOneIsQuail = (it: Listing<Bird>) -> it[0].name == "Quail"
Produces the following error message:
–– Pkl Error ––
Type constraint `firstOneIsQuail` violated.
Value: new Listing { ?; ? }
3 | bird: Listing<Bird>(firstOneIsQuail) = new {
^^^^^^^^^^^^^^^
This is happening because lazy typechecks of mappings and listings will return a new listing. Because the lambda’s argument is defined as it: Listing<Bird>
, the value being passed into the function body is this new listing[2].
Secondly, some constraints became less strict, allowing more values to pass.
The following snippet fails in Pkl 0.26, but passes in Pkl 0.27:
class Bird { name: String }
local nonEmpty = (it: Listing<Bird>) -> !it.isEmpty
birds: Listing(nonEmpty) = new { 1; 2; 3 }
This is because the function parameter it: Listing<Bird>
does not actually check members of the argument until they are accessed, and the function body !it.isEmpty
does not access any members.
To address both of these issues, the rule for executing type constraints has changed. When executing a type annotation, mapping and listing typechecks are always eager. This means that Pkl will check that each member of the mapping/listing conforms to the type parameter.
Java 22+ support
Pkl’s Java libraries now support Java 22 and higher (#876).
Thanks to @sgammon for their contribution!
New standard library method
The pkl:math
standard library module has a new method, called atan2 (#819).
It computes the 2-argument arctangent.
Thanks to @gordonbondon for their contribution!
Overhauled for-generator implementation
The implementation of for-generators has been overhauled (#844).
This fixes some known bugs with the current for-generator implementation, and also improves code health by reducing complexity and removing workarounds and band-aids.
Thanks to @odenix for their contribution!
Java code generator improvements
Improvements have been made to the Java code generator (#792).
In pkl-codegen-java, the --params-annotation
flag now accepts none
as a value, which causes the code generator to skip adding annotations to a constructor’s parameters.
Additionally, the Kotlin API org.pkl.codegen.java.JavaCodeGeneratorOptions.paramsAnnotation
is now nullable.
If null, this also skips adding annotations.
By default, this annotation is set to org.pkl.config.java.mapper.Named
, or none if the --generate-spring-boot-config
flag is set.
This is useful for those compiling Java code with the -parameters
flag set, because the pkl-config-java library is able to map into these classes without any extra annotations.
Thanks to @odenix for their contribution!
Kotlin code generator improvements
Improvements have been made to the Kotlin code generator (#793).
Currently, the output of toString()
on a generated Kotlin class differs depending on if it was generated as a data class or as a regular class.
In Pkl 0.28, this method produces the same format, and matches the format used by data classes.
Thanks to @odenix for their contribution!
pkldoc improvements
pkldoc has a new flag to write real files instead of symlinks (#824).
Currently, pkldoc creates a symlink called "current", which points to the latest non-prerelease version of a package. However, these symlinks can be problematic for some systems.
A new flag, --no-symlinks
, and a similarly named Gradle property, noSymlinks
, instructs pkldoc to write real files instead of symlinks.
Thanks to @netvl for their contributions!
jar:nested:
URIs are accepted by default
To improve integration with Spring Boot, the Pkl evaluator by default now accepts module URIs starting with jar:nested:
(#895).
The --allowed-modules
flag (and the equally named option in other APIs) can be used to configure the set of allowed modules.
Breaking Changes 💔
Things to watch out for when upgrading.
Stricter grammar
As a result of implementing a new parser, the following previously accepted grammar is no longer valid (#917).
Inline object entries
When declaring multiple entries on the same line, a semicolon is now required to separate them.
The following code snippet is currently valid, and becomes a syntax error in 0.28.
obj { ["one"] = 1 ["two"] = 2 }
To fix, insert a semicolon between the two entries:
-obj { ["one"] = 1 ["two"] = 2 }
+obj { ["one"] = 1; ["two"] = 2 }
String escapes
When using custom string delimiters, the escape sequence becomes backslash (\
) plus the number of pounds used to delimit the string.
Due to a parser bug, any extra pounds after this escape sequence are also permitted.
In Pkl 0.28, this becomes a syntax error.
foo = "foo \#(bar)" // Invalid character escape sequence `\#`.
Whitespace between generator members
Inline object spreads, for generators, and when generators all now require either whitespace or a semicolon separator.
foo { ...bar...baz } // Syntax error
Minimum Gradle version bump
The minimum Gradle version for the Gradle Plugin has been bumped to 8.2 (#901).
Minimum Kotlin version bump
For users of Pkl’s Kotlin libraries, the minimum Kotlin version has been bumped to 2.0 (#900).
Java/Kotlin API breaking changes
API | Breakage |
---|---|
org.pkl.core.resource |
Converted into a record; previous getter methods are deprecated. |
org.pkl.core.project |
Converted into a record; previous getter methods are deprecated. |
org.pkl.core.Member |
Converted into a record; previous getter methods are deprecated. |
org.pkl.core |
Converted into a record; previous getter methods are deprecated. |
org.pkl.core.Release |
Converted into a record; previous getter methods are deprecated. |
org.pkl.core.Release |
Converted into a record; previous getter methods are deprecated. |
org.pkl.core.Release |
Converted into a record; previous getter methods are deprecated. |
org.pkl.core.project |
Converted into a record; previous getter methods are deprecated. |
org.pkl.core.evaluatorSettings |
Deprecated; use the constructor instead. |
org.pkl.core.module |
Moved into package org.pkl.core.externalreader |
org.pkl.core.resource |
Moved into package org.pkl.core.externalreader |
org.pkl.core.messaging |
Renamed to ExternalModuleResolverImpl, made package-private |
org.pkl.core.messaging |
Renamed to ExternalResourceResolverImpl, made package-private |
org.pkl.core.module |
Replaced with record org.pkl.core.externalreader.ModuleReaderSpec |
org.pkl.core.resource |
Replaced with record org.pkl.core.externalreader.ResourceReaderSpec |
org.pkl.core.messaging |
Changed properties |
org.pkl.codegen.kotlin |
Renamed to org.pkl.codegen.kotlin.KotlinCodeGeneratorOptions |
org.pkl.codegen.kotlin |
Deprecated without replacement |
org.pkl.codegen.java |
Renamed to org.pkl.codegen.java.JavaCodeGeneratorOptions |
org.pkl.codegen.java |
Deprecated without replacement |
Fat jars no longer shade Truffle
Pkl publishes two fat jars to Maven Central: pkl-tools, and pkl-config-java-all.
Due a version bump in the Truffle library, the package com.oracle.truffle
can no longer be shaded.
For users that use both Pkl and other Truffle languages, this means that their version of Truffle should match Pkl’s version.
Miscellaneous 🐸
-
Documentation improvements (#792, #846, #860, #892, #921, #937, #943, #944, #955, #956, #973).
-
The repository now requires Java 21 or higher to build (#876).
-
Enable multi-jdk testing via
-DmultiJdkTesting=true
flag when building Pkl (#876). -
Allow setting commit id via
-DcommitId
flag when building Pkl (#954).
Bugs fixed 🐜
The following bugs have been fixed.
-
Optimization:
const
members should be cached for all children in the prototype chain (#508) -
codegen-kotlin: Use same toString() representation for data classes and regular classes (#717)
-
Late-bound values of iteratees within nested for/spread fail to resolve for-generator variables (#741)
-
codegen-java/kotlin: Fix generation of equals/hashCode methods (#802)
-
Not possible to render mapping with Int keys in YAML (#878)
-
Parser accepts wrong string escapes (#888)
-
Downstream native-image embedders are broken (#907)
-
Failed type constraint error messages do not show forced members (#918)
-
ANTLR incompatibilities (#927)
-
Doc comments with interleaving comments result in an error (#931)
-
Spread elements inside an object body don’t need separators (#932)
-
Correctly set allowed modules/resoures when external reader scheme contain regex control characters (#941)
-
Bad import analysis fails with "None (cause has no message)" (#949)
Contributors 🙏
We would like to thank the contributors to this release (in alphabetical order):
pkl
CLI, the standard library is parsed during compilation rather than during evaluation. As a result, there is no parsing overhead.