apple/pkl · error · JavaCodeGeneratorException

Annotation `$fqn` is not a valid Java class. The name of…

Error message

Annotation `$fqn` is not a valid Java class.
The name of the annotation should be the canonical Java name of the class, for example, `com.example.Foo`.

What it means

JavaCodeGenerator.toClassName validates that each configured annotation FQN is a canonical Java class name containing at least one dot separating package from class name. A name with no '.' (e.g. `Foo`) cannot be split into package + class, so generation aborts with this message.

Solutions

  1. Prefix the annotation with its full package, e.g. `com.example.Foo` or `java.lang.Override`.
  2. Check the codegen config section (`annotations { ... }`) for keys missing dots.
  3. If using a default-package class, move the class into a package — JavaPoet requires a package.
  4. Re-run generation after fixing the FQN.

Example fix

// before
annotations { ["Deprecated"] = null }
// after
annotations { ["java.lang.Deprecated"] = null }
Defensive patterns

Strategy: validation

Validate before calling

fun isValidJavaFqn(fqn: String) = fqn.contains('.') && fqn.split('.').all { it.matches(Regex("[A-Za-z_$][A-Za-z0-9_$]*")) }

Type guard

fun String?.asCanonicalJavaName(): String? = this?.takeIf { it.lastIndexOf('.') > 0 }

Try / catch

try { generate() } catch (e: JavaCodeGeneratorException) { if ("not a valid Java class" in e.message!!) fixAnnotationFqn() else throw e }

Prevention

When it happens

Trigger: In the pkl-java codegen config, an `annotations` mapping key (or similar FQN setting) is a bare class name like `Foo` or `Override` without a package, so lastIndexOf(".") returns -1.

Common situations: Copy-pasting a simple class name from Java source, configuring java.lang annotations without their package (java.lang.Override), or typos dropping the package prefix.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of apple/pkl@f3efcbfc9b (2026-09-08). Data as JSON: /api/errors/91b6c527d205c744. Report an issue: GitHub.

Appendix: source

Thrown at pkl-codegen-java/src/main/kotlin/org/pkl/codegen/java/JavaCodeGenerator.kt:130

    private val DATA_SIZE = ClassName.get(DataSize::class.java)
    private val DATASIZE_UNIT = ClassName.get(DataSizeUnit::class.java)
    private val PAIR = ClassName.get(Pair::class.java)
    private val COLLECTION = ClassName.get(Collection::class.java)
    private val LIST = ClassName.get(List::class.java)
    private val SET = ClassName.get(Set::class.java)
    private val MAP = ClassName.get(Map::class.java)
    private val PMODULE = ClassName.get(PModule::class.java)
    private val PCLASS = ClassName.get(PClass::class.java)
    private val PATTERN = ClassName.get(Pattern::class.java)
    private val URI = ClassName.get(java.net.URI::class.java)
    private val VERSION = ClassName.get(Version::class.java)

    private const val PROPERTY_PREFIX: String = "org.pkl.config.java.mapper."

    private fun toClassName(fqn: String): ClassName {
      val index = fqn.lastIndexOf(".")
      if (index == -1) {
        throw JavaCodeGeneratorException(
          """
            Annotation `$fqn` is not a valid Java class.
            The name of the annotation should be the canonical Java name of the class, for example, `com.example.Foo`.
          """
            .trimIndent()
        )
      }
      val packageName = fqn.substring(0, index)
      val classParts = fqn.substring(index + 1).split('$')
      return if (classParts.size == 1) {
        ClassName.get(packageName, classParts.first())
      } else {
        ClassName.get(packageName, classParts.first(), *classParts.drop(1).toTypedArray())
      }
    }
  }

  val output: Map<String, String>

View on GitHub (pinned to f3efcbfc9b)