pkl-config-java Library

The pkl-config-java library builds upon pkl-core. It offers a higher-level API specifically designed for consuming application runtime configuration.

Installation

The pkl-config-java library is available from Maven Central. It requires Java 17 or higher.

Gradle

To use the library in a Gradle project, declare the following dependency:

  • Kotlin

  • Groovy

build.gradle.kts
dependencies {
  implementation("org.pkl-lang:pkl-config-java:0.28.0-SNAPSHOT")
}

repositories {
  maven(url = "https://s01.oss.sonatype.org/content/groups/public")
}
build.gradle
dependencies {
  implementation "org.pkl-lang:pkl-config-java:0.28.0-SNAPSHOT"
}

repositories {
  maven { url "https://s01.oss.sonatype.org/content/groups/public" }
}

Unlike pkl-config-java, pkl-config-java-all is a fat Jar with renamed third-party packages to avoid version conflicts.

Maven

To use the library in a Maven project, declare the following dependency:

pom.xml
<project>
  <dependency>
    <groupId>org.pkl-lang</groupId>
    <artifactId>pkl-config-java</artifactId>
    <version>0.28.0-SNAPSHOT</version>
  </dependency>
  <repositories>
    <repository>
      <id>sonatype-s01</id>
      <name>Sonatype S01</name>
      <url>https://s01.oss.sonatype.org/content/groups/public</url>
    </repository>
  </repositories>
</project>

Unlike pkl-config-java, pkl-config-java-all is a fat Jar with renamed third-party packages to avoid version conflicts.

Usage

Consuming Configuration

The ConfigEvaluator class loads and evaluates Pkl modules. If evaluation succeeds, a Config object is returned. Otherwise, a PklException with error details is thrown.

The returned Config object represents the root of the Pkl configuration tree. Intermediate and leaf nodes are also represented as Config objects.

Config objects offer methods to

  • convert their Pkl value to a Java value of the specified type.

  • navigate to child nodes.

Let’s see this in action:

Config config;
try (var evaluator = ConfigEvaluator.preconfigured()) { (1)
  config = evaluator.evaluate(
    ModuleSource.text("pigeon { age = 5; diet = new Listing { \"Seeds\" } }")); (2)
}
var pigeon = config.get("pigeon"); (3)
var age = pigeon.get("age").as(int.class); (4)
var diet = pigeon.get("diet").as(JavaType.listOf(String.class)); (5)
1 Create a preconfigured ConfigEvaluator. To create a customized evaluator, start from ConfigEvaluatorBuilder.preconfigured() or ConfigEvaluatorBuilder.unconfigured(). The evaluator should be closed once it is no longer needed. In this example, this is done with a try-with-resources statement. Note that objects returned by the evaluator remain valid after calling close().
2 Evaluate the given text. Other evaluate methods read from files, URLs, and other sources. If evaluation fails, an PklException is thrown.
3 Navigate from the config root to its "pigeon" child.
4 Navigate from "pigeon" to "age" and get the latter’s value as an int. If conversion to the requested type fails, a ConversionException is thrown.
5 Navigate from "pigeon" to "diet" and get the latter’s value as a List<String>. Note the use of JavaType.listOf() for creating a parameterized type literal. Similar methods exist for sets, maps, and other generic types.

A ConfigEvaluator caches module sources and evaluation results. To clear the cache, for example to evaluate the same module again, close the evaluator and create a new one.

For a ready-to-go example with full source code, see config-java in the pkl-jvm-examples repository.

Object Mapping

When a Config object needs to convert its Pkl value to a Java value, it delegates the conversion to ValueMapper. ValueMapper can convert an entire PModule or any part thereof.

A ValueMapper instance can be configured with many different Pkl-to-Java value conversions. ValueMapper.preconfigured() creates an instance configured with conversions from Pkl values to:

  • Number types

  • Strings

  • Enums

  • Collections

  • Arrays

  • java.util.Optional

  • java.time.Duration

  • java.net.URI/URL

  • etc.

Additionally, a preconfigured ValueMapper instance can convert Pkl objects to Java objects with equally named properties that are settable through a constructor. This conversion works as follows:

  1. Find the Java class constructor with the highest number of parameters.

  2. Match constructor parameters with Pkl object properties by name.

    Unmatched constructor parameters result in a conversion error. Unmatched Pkl object properties are ignored.

  3. Convert each Pkl property value to the corresponding constructor parameter’s type.

  4. Invoke the constructor.

The Pkl object’s runtime type is irrelevant to this conversion. Hence, typed and dynamic Pkl objects are equally supported.

To perform this conversion, ValueMapper needs a way to obtain the Java constructor’s parameter names. They need to be provided in one of the following ways:

  • Annotate constructor with java.beans.ConstructorProperties.

  • Annotate parameters with Named.

  • Annotate parameters with javax.inject.Named.

  • Set the Java compiler flag -parameters.

For a complete object mapping example, see:

Together with code generation, object mapping provides a complete solution for consuming Pkl configuration as statically typed Java objects. Java code never drifts from the configuration structure defined in Pkl, and the entire configuration tree can be code-completed in Java IDEs.

Value Conversions

The Pkl-to-Java value conversions that ship with the library are defined in Conversions (for individual conversions) and ConverterFactories (for families of conversions). To implement and register your own conversions, follow these steps:

  1. For conversions from a single source type to a single target type, implement a Conversion.

    Example: Conversions.pStringToCharacter converts a single-character pkl.base#String to java.lang.Character.

  2. For conversions from one or multiple source types to one or multiple target types, implement a ConverterFactory.

    Example: ConverterFactories.pCollectionToCollection converts any pkl.base#Collection to any implementation of java.util.Collection<E>, for any E.

    Converter factories are called once per combination of source type and (possibly parameterized) target type. The returned `Converter`s are cached.

  3. Create a ValueMapperBuilder, add all desired conversions, and build a ValueMapper.

  4. Either use the ValueMapper directly, or connect it to a ConfigEvaluator through ConfigEvaluatorBuilder.

Further Information

Refer to the Javadoc and sources published with the library, or browse the library’s main and test sources.