Pkl 0.29 Release Notes
Pkl 0.29 was released on July 24th, 2025.
The latest bugfix release is 0.29.1. (All Versions)
This release brings support for working with binary data, and also a new setting to control HTTP rewriting.
The next release (0.30) is scheduled for October 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 💖
Bytes
standard library class
A new standard library class is introduced, called Bytes
(#1019).
Currently, Pkl does not have a built-in way to describe binary data. In situations where binary data is needed, the common pattern is to use a Base64 string. This is sufficient in simple cases, but still introduces many shortcomings:
-
pkl eval
can only produce UTF-8 encoded strings. -
It is not possible to compute the checksum of binary content (except in the case of
read()
). -
It is hard to interop with configuration formats that allow binary data.
-
It is hard to do transformations on binary data.
To address these shortcomings, the Bytes
class is introduced.
This is a data type representing a sequence of bytes.
myBytes = Bytes(0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x21) (1)
1 | ASCII bytes for the string "Hello, World!" |
To learn more about this feature, consult SPICE-0013.
Emitting binary output
A new property called bytes
is added to FileOutput
.
The CLI has been changed to evaluate this property, and write its contents to the designated output location.
This change means that Pkl modules can now output binary content. For example, here is a simple module that outputs bytes:
output {
bytes = Bytes(1, 2, 3, 4) (1)
}
1 | Write bytes 1, 2, 3, 4 |
The same change applies when evaluating in multiple-file output mode.
output {
files {
["foo.bin"] {
bytes = Bytes(1, 2, 3, 4) (1)
}
}
}
1 | Write bytes 1, 2, 3, 4 to a file called foo.bin . |
Rendering Bytes
Out of the box, only the plist
and pcf
formats are able to render Bytes
.
For other formats, a renderer needs to be defined.
output {
renderer = new JsonRenderer {
[Bytes] = (it) -> it.base64 (1)
}
}
1 | Render bytes as a base64 string in JSON. |
Using Bytes
from language bindings
Users of Pkl’s language bindings also benefit from the new type.
When using code generation, the Bytes
data type will turn into the following types:
Language | Type |
---|---|
Java |
|
Kotlin |
|
Go |
|
Swift |
|
Maintainers of other language bindings are encouraged to map Bytes
into the corresponding binary type in their language.
Bytes
versus List<UInt8>
Conceptually, Bytes
is very similar to List<UInt8>
, because both are data types that describe sequences of UInt8
values.
However, they have different performance characteristics.
A List
is a persistent data structure.
This means that creating modified copies of lists is very cheap.
However, lists have more overhead per element.
A List<UInt8>
with 1000 elements takes up about 5.4 kilobytes of heap space, whereas the same data in Bytes
takes roughly 1 kilobyte.
HTTP Rewrites and Package Mirroring
A new evaluator setting is introduced, which rewrites URLs before making outbound HTTP calls (#1062, #1127, #1133).
This setting can be configured via CLI flag --http-rewrite
, and also in other ways:
-
A builder option in
org.pkl.core.EvaluatorBuilder
-
A builder option in
org.pkl.executor.ExecutorOptions
-
A new property in
CreateEvaluatorRequest
in the Message Passing API -
A new property in the Gradle plugin
-
A new property in
pkl.EvaluatorSettings#Http
The main use-case for this setting is to enable package mirroring. For example, let’s assume that the following mirrors exist:
Original | Mirror |
---|---|
https://pkg.pkl-lang.org |
https://my.internal.mirror/pkg-pkl-lang |
https://github.com |
https://my.internal.mirror/github |
A user of the CLI can use these mirrors with the following settings.
amends "pkl:settings"
http {
rewrites {
["https://pkg.pkl-lang.org/"] = "https://my.internal.mirror/pkg-pkl-lang/"
["https://github.com/"] = "https://my.internal.mirror/github/"
}
}
To learn more about this feature, consult SPICE-0016.
Noteworthy 🎶
Ready when you need them.
pkldoc, pkl-codegen-java, pkl-codegen-kotlin executables
The pkldoc, pkl-config-java and pkl-config-kotlin CLIs are published as their own executables (#1023).
Currently, these tools have excellent support when called from within the pkl-gradle plugin.
However, the story for using these as CLIs is much clunkier.
Users must call Java by including their published .jar
files, as well as all of their dependencies via the -classpath
argument.
In 0.29, they are all being published as executables. In particular, pkldoc is being published as both a Java executable and a native executable. On the other hand, pkl-codegen-java and pkl-codegen-kotlin are published as Java executables only.
For more information, see their download instructions:
Java API changes
Resource Readers SPI added to preconfigured evaluators
A change was made to the preconfigured evaluators (#1094).
Currently, they add module key factories from service providers, but not resource readers.
This means that users of pkl-spring, for example, cannot add custom resource readers. This is also inconsistent (in the preconfigured evaluator, import can use custom schemes, but not read).
In Pkl 0.29, any resource reader registered via the Service Provider Interface will be added to the preconfigured evalutors.
New methods
New methods are introduced to the Java API.
-
org.pkl.core.Evaluator.evaluateOutputBytes
-
org.pkl.core.http.HttpClient.Builder.setRewrites
-
org.pkl.core.http.HttpClient.Builder.addRewrite
-
org.pkl.executor.ExecutorOptions.httpRewrites
-
org.pkl.config.java.ConfigEvaluatorBuilder.getHttpClient
-
org.pkl.config.java.ConfigEvaluatorBuilder.setHttpClient
Standard Library changes
New properties, methods, classes and typealiases have been added to the standard library (#1019, #1053, #1063, #1144).
pkl
CLI changes
Aggregated JUnit reports
A new set of CLI flags are introduced: --junit-aggregate-reports
, and --junit-aggregate-suite-name
.
Collectively, these flags tell Pkl to combine the JUnit reports into a single file, instead of creating a file per Pkl module.
Thanks to @gordonbondon for contributing to this feature!
shell-completion subcommand
A new subcommand called shell-completion
has been added to the Pkl CLI.
This command produces autocompletion scripts for a given shell.
The following example installs shell completions for fish shell:
pkl shell-completion fish > "~/.config/fish/completions/pkl.fish"
Thanks to @gordonbondon for contributing to this feature!
@Generated
annotation for Java/Kotlin codegen
Classes generated by the Java and Kotlin code generator can optionally recieve new annotation called Generated
(#1075, #1115).
This behavior is toggled with the --generated-annotation
CLI flag, or the similarly named Gradle property.
When enabled, classes are annotated with org.pkl.config.java.Generated
.
This allows users of JaCoCo to exclude Pkl-generated classes from coverage reports.
Thanks to @arouel for their contributions here!
Breaking Changes 💔
Things to watch out for when upgrading.
Standard library deprecations
The following methods have been deprecated in the standard library:
Method name | Details |
---|---|
|
Replaced with |
|
Replaced with |
|
Replaced with |
|
Replaced with |
Grammar changes
Block comment nesting is removed
Currently, block comments can be nested.
For example, the comment /* /* my comment */ */
is two block comments; one outer comment and one inner comment.
However, this has some drawbacks.
-
Block comments can be possibly be closed incorrectly, like
/* /* my comment */
. However, this is a valid block comment in most languages. -
Pkl’s syntax highlighters do not support block comment nesting, leading to highlighting that is inconsistent with Pkl’s parser.
To improve user experience, block comment nesting is removed.
As a result, block comments are always terminated upon the first */
.
Shebang line can only appear at the start of a file
The grammar around shebang comments has been made stricter.
Pkl files allow for a shebang comment. Currently, this comment can appear anywhere in a file. In 0.29, this comment must appear at the start of a file.
Opaque file:
URIs are invalid
A new rule is introduced to treat opaque file:
URIs as errors (#1087).
Opaque file URIs are URIs whose scheme-specific part does not start with /
.
For example, file:foo/bar.txt
is an opaque URI.
Currently, this has the unintentional behavior of: look for file foo/bar.txt
from the process working directory.
These are effectively dynamics imports; from a single import, we can’t statically determine what file is actually being imported.
According to RFC-8089, file
URIs must have paths that start with /.
So, these are actually not valid URIs.
In Pkl 0.29, it is an error to load a module or resource with an opaque file:
URI.
To import or read a relative path, omit file: from the import string. For example, import("foo/bar.txt") instead of import("file:foo/bar.txt") .
|
New base module names: Bytes
and Charset
Two new names are introduced to the base module: Bytes
and Charset
.
That means that any code that currently references these names on implicit this
will break (#1019).
The following snippet demonstrates this breaking behavior.
In 0.28 and below, obj2.prop
resolves to string "my bytes".
In 0.29, it resolves to class Bytes
in the base module.
obj1 {
Bytes = "my bytes"
}
obj2 = (obj1) {
prop = Bytes
}
To make this code continue to have the same meaning, an explicit this
reference is required.
obj1 {
Bytes = "my bytes"
}
obj2 = (obj1) {
- prop = Bytes
+ prop = this.Bytes
}
This only affects code that references these names off of implicit this. Code that references the name from the lexical scope will continue to work as-is.
To learn more about name resolution, consult the language reference.
jpkl is not published to Maven Central
Due to a breakage in release pipeline, the jpkl
executable is not published to Maven Central (#1147).
It is still available to download as a GitHub release asset.
Bugs fixed 🐜
The following bugs have been fixed.
-
New parser fails on nested multi line comments (#1014)
-
Fix package dependency links when generating pkldoc (#1078)
-
Don’t show 100% when number of failures is rounded up (#1110)
-
Quoting the module name crashes pkl (#1111)
-
Shebang comment parsing is too lenient (#1125)
-
CLI: noProxy option in settings.pkl and PklProject are ignored (#1142)
Contributors 🙏
We would like to thank the contributors to this release (in alphabetical order):