Common Compiler Errors
Diagnosing what the annotation processor and type checker tell you
- How to find your compiler message in the table below and go straight to its entry.
- Which messages stop the build, which stop it only under
-Werror, and which stop nothing. - The most common errors from the
@Generate*annotations and how to fix them. - Errors that surface from
@ImportOpticsandOpticsSpecinterfaces, including the spec-method hint annotations. - Type-inference traps when chaining the Focus DSL through
.each(),.via(), andtraverseOver. - Free Monad DSL pitfalls and the witness-type errors they produce.
This page is for the moment a build fails and you want to know what the message means. Find the fragment your compiler printed in the table below and follow it to its entry. Every entry says what the message means, gives the fix, and keeps the reasoning in a Why you can open if you want it.
- An error stops the build. Almost everything here is an error.
- A warning stops the build only under
-Werror. A processor warning cannot be suppressed, so the remedy is the fix rather than an annotation. - A note stops nothing. It shows in the compiler output as
Note: ..., and tells you that something you asked for was quietly not applied.
Rows below and headings on the page say which, wherever it is not an error.
Find your message
From @GenerateLenses, @GenerateFocus, @GenerateTraversals and friends (entries):
| The message says | What it means |
|---|---|
cannot find symbol: class XLenses | The processor has not run, or the IDE has not indexed the generated sources |
can only be applied to records | @GenerateLenses or a sibling is on a class |
can only be applied to sealed interfaces or enums | @GeneratePrisms is on something else |
names a type variable | The @GenerateIsos method's Iso type is not fully concrete |
'x' is not static | The @GenerateIsos method is an instance method |
'x' takes parameters | The @GenerateIsos method takes arguments |
does not return an Iso with both type arguments | The @GenerateIsos method returns something else |
cannot be reached from 'p' | The @GenerateIsos method is not visible from the generated package |
has a wildcard type argument, has a raw Set | A widened container is raw, or has a wildcard type argument |
no traversal was generated for component | Note. @GenerateTraversals found no generator for a container |
Multiple TraversableGenerator SPI providers with equal priority | Warning. Two generators claim one type and neither outranks the other |
the annotation on record component 'X.y' is not applied | Note. @TraverseField is on something that is not a Kind with a declared witness |
names a witness the processor does not recognise | Note. A Kind field's witness has no registered Traverse, so nothing widens it |
From @ImportOptics and spec interfaces (entries):
| The message says | What it means |
|---|---|
carries no copy strategy annotation | A spec Lens method names none of the four copy strategies |
is a default method | A spec interface method has a body |
which is a type variable | OpticsSpec<S> names a type parameter rather than a type |
which names the raw type 'Box' | The source type is missing its type arguments |
rather than as the List interface | @ThroughField's lens focuses a concrete container, or another interface |
which the spec does not declare | @ThroughField has no lens for the field to compose with |
hands back as 'String' | @ThroughField's declared focus is not what the traversal returns |
is not a subtype of source type | @InstanceOf names a class outside the hierarchy |
which the test cannot narrow to | The focus promises a type argument instanceof cannot check |
carries type parameters of its own | @InstanceOf names an Outer<X>.Inner<Y>, which instanceof cannot write |
narrows to '...', which is not a '...' | The @InstanceOf class is not assignable to the declared focus |
does not resolve to a type | copyConstructor is not a fully qualified class name |
which 'S' does not extend or implement | copyConstructor names a type that is not a supertype |
is not public and so cannot be named from | copyConstructor names a type the generated class cannot see |
and no constructor accepts | No copy constructor takes the supertype you named |
is written with a wildcard type argument | A constructor rebuild cannot be written for a wildcard source type |
focuses '...', which is not a '...' | A generated prism's focus is a value rather than a variant of the source |
cannot find symbol, inside XPrisms.java | A @MatchWhen predicate or getter name is misspelt |
requires a prism hint annotation | A spec Prism method has neither @InstanceOf nor @MatchWhen |
From @GeneratePathBridge and @PathVia (entries):
| The message says | What it means |
|---|---|
which no Path wraps | The method returns a type outside the bridged set |
names the raw type 'Y' | The signature is missing type arguments somewhere |
is the wildcard '?' | A bridged Validated names its error type as a wildcard |
has the same name as 'Y's | A method type parameter hides one of the interface's |
the bridge cannot call 'x' | The method is static or private |
is already taken | Two @PathVia methods produce the same bridge signature |
is not a method name | @PathVia(name = ...) is not a Java identifier |
cannot be reached from 'p' | Under targetPackage, part of the signature is not visible there |
no @PathVia method was found | Warning. The interface has nothing to bridge |
From javac, on code you wrote (Focus DSL chains, Free Monad):
| The message says | What it means |
|---|---|
ambiguity, or Object turning up in a long chain | traverseOver's witness type is not pinned |
Incompatible types, after .each().via() | Usually one .each() too many |
Cannot infer type argument(s) | Only the final each() in a chain can infer its element type |
::new rejected as a BiFunction | A single-component record has no two-argument constructor |
Sealed or non-sealed local classes are not allowed | A sealed interface is declared inside a method body |
Cannot resolve method 'flatMap(...)' | Two Free witness types are being mixed |
Free<F, A> cannot be converted to A | The program was never handed to an interpreter |
Where the message came from
Three different things can reject your code, and knowing which one spoke narrows the search:
flowchart TD
D["Your declaration<br/>@Generate*, or a spec interface"]
P{"Does the processor<br/>accept it?"}
R["Refused at your declaration.<br/>Most of this page"]
G["Generated file written"]
J{"Does javac accept<br/>the generated file?"}
JG["cannot find symbol,<br/>inside a class you did not write"]
C{"Does javac accept<br/>your call site?"}
CC["Focus DSL chain and<br/>Free Monad errors"]
OK(["Builds"])
D --> P
P -->|no| R
P -->|yes| G
G --> J
J -->|no| JG
J -->|yes| C
C -->|yes| OK
C -->|no| CC
classDef step fill:#8caaee,stroke:#1e66f5,color:#232634
classDef decision fill:#e5c890,stroke:#df8e1d,color:#232634
classDef error fill:#e78284,stroke:#d20f39,color:#232634
classDef ok fill:#a6d189,stroke:#40a02b,color:#232634
class D,G step
class P,J,C decision
class R,JG,CC error
class OK ok
Most of this page is the first branch. The processor reads your declaration, finds a shape it cannot write code for, and says so where you wrote it. Those messages name the element they rejected, so reading the processor's own output first is quicker than working backwards from a cannot find symbol further down the build.
A cannot find symbol: class XLenses sits outside the diagram altogether: it means the processor never ran.
@GenerateLenses / @GenerateFocus / @GenerateTraversals
"cannot find symbol: class XLenses"
The annotation processor has not run yet, or the IDE has not picked up the generated sources directory.
Fix. Run a build (./gradlew build or mvn compile). After the build completes, refresh the project in your IDE so it indexes build/generated/sources/annotationProcessor/java/main (Gradle) or target/generated-sources/annotations (Maven).
"@GenerateLenses: can only be applied to records, but 'Foo' is a class"
@GenerateLenses, @GenerateFocus, @GenerateFolds, @GenerateGetters, @GenerateSetters and @GenerateTraversals only apply to records.
Fix. Convert the class to a record. If the type is third-party and you cannot change it, use @ImportOptics on a package-info.java or a spec interface instead.
Why
Why
The annotations target TYPE, so javac itself is happy; the message comes from the processor. The wording varies: @GenerateLenses and @GenerateFocus name the offending type, while the others emit the shorter "The @GenerateTraversals annotation can only be applied to records."
"The @GeneratePrisms annotation can only be applied to sealed interfaces or enums."
@GeneratePrisms requires a sealed interface or an enum. A sealed abstract class is rejected too, despite being sealed, because the processor tests the element kind rather than the modifier.
Fix. Make the type a sealed interface and declare its permits clause, or convert it to an enum.
A non-sealed interface passes the processor's guard and produces an empty XPrisms class with no diagnostic at all. If your prisms class exists but has no methods, an unsealed interface is why.
"@GenerateIsos: the iso returned by 'x' names a type variable"
One of the returned Iso's two type arguments is, or contains, a type variable. Both <T> Iso<Box<T>, T> boxIso() and an instance method of a Holder<X> returning Iso<Box<X>, X> do this.
Fix. Give the iso concrete type arguments where the method is declared (Iso<Box<String>, String>), or drop @GenerateIsos and call the method directly.
Why
Why
What gets generated is a public static final field, and a field has nowhere to declare one, so it would name a variable nothing brings into scope.
Note this is about what the iso names, not what the method declares: <T> Iso<Box, String> boxIso() is fine, because T is inferred at the call and never reaches the field's type.
A declaration that produces it
A declaration that produces it
record Box<T>(T content) {}
final class BoxIsos {
@GenerateIsos
static <T> Iso<Box<T>, T> box() {
return Iso.of(Box::content, Box::new);
}
}
"@GenerateIsos: 'x' is not static"
The annotated method is an instance method. The generated field initialises itself with a static call, and there is no instance to make it on.
Fix. Make the method static.
A declaration that produces it
A declaration that produces it
record Point(int x) {}
final class PointIsos {
@GenerateIsos
Iso<Point, Integer> point() {
return Iso.of(Point::x, Point::new);
}
}
"@GenerateIsos: 'x' takes parameters"
The annotated method takes arguments. The generated field initialises itself by calling the method with none, and there is nothing for it to pass.
Fix. Take the arguments away, or drop @GenerateIsos and call the method directly.
A declaration that produces it
A declaration that produces it
record Point(int x) {}
final class PointIsos {
@GenerateIsos
static Iso<Point, Integer> point(int scale) {
return Iso.of(Point::x, Point::new);
}
}
"@GenerateIsos: 'x' does not return an Iso with both type arguments"
The method returns a void, a primitive, an array, a raw Iso, or something that is not an Iso at all. The generated field is typed from the two arguments of the returned Iso, and none of those carries them.
Fix. Return Iso<S, A> naming both, as Iso<Point, Tuple2<Integer, Integer>>.
A declaration that produces it
A declaration that produces it
final class PointIsos {
@GenerateIsos
static String point() {
return "not an Iso";
}
}
"@GenerateIsos: 'x' cannot be reached from 'p'"
The generated class lives in package p and calls the method from there, but the method, or a type enclosing it, is private, protected or package-private somewhere else. Most often seen with targetPackage.
Fix. Make the method and its enclosing types public, or generate into the package they are already visible from.
A declaration that produces it
A declaration that produces it
final class LengthIsos {
@GenerateIsos(targetPackage = "com.example.optics")
static Iso<String, Integer> length() {
return Iso.of(String::length, "x"::repeat);
}
}
"@GenerateFocus: record component 'X.y' has a wildcard type argument in Set<? extends T>"
A container that the processor widens through an optic instance is declared raw, or with a wildcard type argument. The same error covers Set, Collection, Map, Either, Try and every other such container, and is also reported as "has a raw Set".
Fix. Name the type argument, Set<Leaf> rather than Set<? extends Leaf>, or drop @GenerateFocus from the record and keep @GenerateLenses and @GenerateTraversals, which compose no optic instance and take the component as written. See Custom Containers.
Why
Why
That instance, EachInstances.setEach() or Affines.eitherRight(), has its own type arguments worked out from the component's type. A raw container gives javac nothing to work from, and a wildcard stands for no one type. Optional, Maybe and List are exempt: they widen through the no-argument .some() and .each(), whose element type is free to be whatever the field says.
"@GenerateTraversals: no traversal was generated for component 'X.y' of type Deque<T>" (a note)
@GenerateTraversals asks the TraversableGenerator SPI for each record component, and no generator on the annotation processor path claimed this one.
Fix. Declare the component as a container a generator supports: List, Set, Collection, Map, Optional, an array, or a type one of the generator plugins covers. For a raw container, give it its element type. For a third-party type, put a TraversableGenerator for it on the annotation processor path. A mixed record, one supported container beside one unsupported, keeps the traversals it can have and carries the note for the one it cannot; the note is the reminder, not a gate. A record that wants no traversal for any of its components should not carry @GenerateTraversals at all; @GenerateLenses on its own still gives every component a lens.
Why
Why
The component unmistakably holds elements, being a java.util.Collection or a java.util.Map by erasure. Not generating for it is a gap rather than the expected outcome, and the generated class would otherwise compile with the method silently missing. The second sentence names the unsupported type (No TraversableGenerator on the annotation processor path supports Deque). The same note is raised, with a different second sentence, for a container a generator did claim but cannot read: a raw List or Set "is written without a type argument, so there is no element type to focus", and a generator whose focused type argument the type does not have says which argument it wanted.
A component that is not a container at all is passed over without comment: a String, an int, or a java.nio.file.Path, which implements Iterable and is why a bare Iterable is not the bar.
It is a note rather than a warning on purpose. @GenerateTraversals has no per-component opt-out, and a processor warning cannot be suppressed, so a warning would have failed every -Werror build with no remedy short of changing the record. A note shows in the compiler output as Note: ... and fails nothing.
"Multiple TraversableGenerator SPI providers with equal priority (N) support type X" (a warning)
Two generators on the annotation processor path both claim the type, and neither outranks the other: supports() answers true from both at the same priority().
Fix. Rank one of the providers: return PRIORITY_OVERRIDE from the one that should win, or PRIORITY_FALLBACK from the one that should yield, or drop one from the annotation processor path. The message names both provider classes. A consuming build running javac with -Werror turns the warning into an error, so the ranking is the remedy, not optional tidiness. See How Plugin Discovery Works.
Why
Why
Selection is still deterministic, the first registered wins, but which one that is depends on registration order alone, which is what the warning points out. The same warning is raised whichever annotation asks: @GenerateTraversals, @GenerateFocus widening or @ImportOptics.
"@TraverseField: the annotation on record component 'X.y' is not applied" (a note)
@TraverseField names a Traverse for a Kind<F, A> component with a declared witness, and this component is not one.
Fix. Declare the component as the Kind<F, A> the Traverse is written for, Kind<TreeKind.Witness, Tree> for a Traverse<TreeKind.Witness>, with both type arguments given and a witness that is a type rather than a bare or ? super wildcard, a type variable of the record, or a wildcard bounded by one; or drop the annotation and take the path the component gets on its own, applying traverseOver yourself where the witness is known. See Custom Kind Types with @TraverseField.
Why
Why
The second sentence says which way: the component is not declared as a Kind at all (List<String> is not declared as a Kind<F, A> component), the Kind is written raw and so names neither a witness nor an element, its witness is a bare or ? super wildcard (Kind<?, String>) that stands for no type and so names no Traverse instance, or its witness is one of the record's own type variables (Kind<F, String> in a Holder<F>, or Kind<? extends F, String>, whose wildcard resolves to F), which stands for any witness, while a Traverse is written for one. The component keeps the path it would have had without the annotation, a plain FocusPath, or .each() for a List, which compiles and is correct as far as it goes; what is missing is the traversal the annotation asked for.
It is a note rather than an error because nothing is broken: the generated class is sound, and the same declaration without the annotation passes without comment. A warning cannot be suppressed and would fail a -Werror build with no remedy short of editing the record.
A declaration that draws it
A declaration that draws it
@GenerateFocus
record Inbox(
@TraverseField(traverse = "org.higherkindedj.hkt.list.ListTraverse.INSTANCE")
List<String> messages) {}
"@GenerateFocus: record component 'X.y' names a witness the processor does not recognise" (a note)
The component is a Kind<F, A> whose witness is one of Higher-Kinded-J's own, but not one the Focus processor has a Traverse registered for. Nothing widens it, so the generated method is a plain FocusPath focusing the Kind.
Fix. Add @TraverseField naming the Traverse instance for the witness, or keep the plain path and apply traverseOver yourself. See Kind Field Support.
Why
Why
A witness of your own draws no note, since not traversing it is an ordinary choice; a library witness with no registered Traverse is a gap you would want to hear about. The note is written once for the component, however many navigators reach the record.
A declaration that draws it
A declaration that draws it
@GenerateFocus
record Batch(Kind<NonEmptyListKind.Witness, String> items) {}
@ImportOptics and OpticsSpec interfaces
"@ImportOptics: Lens method 'x' carries no copy strategy annotation"
A method on an OpticsSpec interface returns Lens<S, A> but carries none of @Wither, @ViaConstructor, @ViaCopyAndSet or @ViaBuilder, so the processor has no way to know how the external type rebuilds itself.
Fix. Add the appropriate hint based on how the source type is copied. See Optics for External Types and Database Records with JOOQ for the full strategy table.
A declaration that produces it
A declaration that produces it
final class Session {
public String user() {
return "";
}
}
@ImportOptics
interface SessionOpticsSpec extends OpticsSpec<Session> {
Lens<Session, String> user();
}
"'XOpticsSpec.foo' is a default method"
A spec interface declares a default method. A method body cannot be read during annotation processing, so there is nothing for the generated class to carry.
Fix. Keep the spec interface to annotated abstract methods. Composed optics belong in a static method on the interface, or in an ordinary utility class; either one calls the generated statics by name, for example JsonNodeOptics.object().andThen(...).
A declaration that produces it
A declaration that produces it
final class Session {
public String user() {
return "";
}
}
@ImportOptics
interface SessionOpticsSpec extends OpticsSpec<Session> {
default Lens<Session, String> user() {
return Lens.of(Session::user, (session, user) -> session);
}
}
"'XOpticsSpec' declares OpticsSpec<S>, which is a type variable"
The spec interface is generic, and its own type parameter is the source type: interface BoxOpticsSpec<S extends Box> extends OpticsSpec<S>.
Fix. Name the type the optics are for as the type argument, with its own type arguments where it has any: OpticsSpec<Box>. Where the bound names a single type that is not raw, the message suggests it for you.
A source type that is itself generic is supported, and the spec names its own type parameters: interface BoxOpticsSpec<U> extends OpticsSpec<Box<U>> generates static <U> Lens<Box<U>, String> label(). See Spec Interfaces for which parameters a generated method declares. It is only a bare type variable, standing for the whole source type, that has no source to read.
Why
Why
Optics are generated against one named type, read for its members and rebuilt through its constructor, wither or setter, so a type parameter standing for whatever a caller picks has nothing to generate from. An array source type produces the same diagnostic with a different opening, declares OpticsSpec<String[]>, which is an array type, and the same remedy.
A declaration that produces it
A declaration that produces it
class Session {}
@ImportOptics
interface SessionOpticsSpec<S extends Session> extends OpticsSpec<S> {
@Wither("withUser")
Lens<S, String> user();
}
"'XOpticsSpec' declares OpticsSpec<Box>, which names the raw type 'Box'"
The source type names a generic type without its arguments.
Fix. Name the raw type's arguments in the OpticsSpec clause: OpticsSpec<Box<String>>, OpticsSpec<Outer<String>.Holder>, OpticsSpec<Box<List<String>>>. A spec whose optics should stay generic declares its own type parameters and passes them on, interface BoxOpticsSpec<U> extends OpticsSpec<Box<U>>, as above. See Spec Interfaces.
Why
Why
Every generated optic repeats the source type verbatim, so the generated file, which you cannot edit, would carry a [rawtypes] warning that the @SuppressWarnings on your own spec does not cover, and a @ViaConstructor rebuild read under a raw type erases its parameters into an [unchecked] call besides. Three shapes draw the error: the source type itself written bare (OpticsSpec<Box> for a Box<X>), a member type behind a generic outer written bare (OpticsSpec<Outer.Holder>, raw by JLS 4.8 even though Holder declares nothing of its own), and a raw type argument (OpticsSpec<Box<List>>). Raw is not the same as bare: a non-generic source type, or a static nested type of a generic outer, has no arguments to supply and is accepted as written.
A declaration that produces it
A declaration that produces it
record Box<T>(T content) {
Box<T> withContent(T content) {
return new Box<>(content);
}
}
@ImportOptics
interface BoxOpticsSpec extends OpticsSpec<Box> {
@Wither("withContent")
Lens<Box, String> content();
}
"@ThroughField: '...' reaches field 'items', which is declared as ArrayList<String> rather than as the List interface"
The spec's own lens for the field focuses something narrower than a container interface: a concrete container such as ArrayList, or another interface such as Deque. Auto-detection matches List, Set, Collection, Map, Optional and reference-type arrays, on the interface itself.
Fix. Name a traversal that rebuilds the declared type, Traversals.forIterableCollecting(ArrayList::new) for a list-shaped container or Traversals.forMapValuesCollecting(TreeMap::new) for a map, exposed as a static method and named fully qualified: @ThroughField(field = "items", traversal = "com.example.MyTraversals.forArrayList()"). Where the type is yours, declaring the field as the interface (List<String>) is the simpler route. See @ThroughField auto-detection.
Why
Why
The type the message names is that lens focus. Each standard traversal promises no more than the interface type (Traversals.forList() hands back an unmodifiable List), and the composed optic writes that value back into the field through the lens; a field declared as something narrower, a concrete container (ArrayList, HashSet, TreeMap) or another interface (Deque, SortedSet), cannot take it, so the generated traversal would throw ClassCastException on first use, on a read as well as a write. The message names the interface the field's type implements. An array of a primitive (int[]) draws the sibling message: the array traversal walks an Object[], which an int[] is not.
A declaration that produces it
A declaration that produces it
final class Shelf {
public ArrayList<String> items() {
return new ArrayList<>();
}
public Shelf withItems(ArrayList<String> items) {
return this;
}
}
@ImportOptics
interface ShelfOpticsSpec extends OpticsSpec<Shelf> {
@Wither("withItems")
Lens<Shelf, ArrayList<String>> items();
@ThroughField(field = "items")
Traversal<Shelf, String> eachItem();
}
"@ThroughField: '...' composes through a lens named 'items', which the spec does not declare"
A @ThroughField traversal is generated as the spec's own lens for the field composed with the container traversal, Spec.items().andThen(...). The spec has to declare that Lens<S, F> items() alongside it, with its copy strategy, and this one does not. Without the lens the generated file could only fail with cannot find symbol, so the processor refuses the declaration instead.
Fix. Declare the lens method for the field on the spec, or use @TraverseWith to name a traversal over the source type that stands on its own.
A declaration that produces it
A declaration that produces it
final class Shelf {
public List<String> items() {
return List.of();
}
}
@ImportOptics
interface ShelfOpticsSpec extends OpticsSpec<Shelf> {
@ThroughField(field = "items")
Traversal<Shelf, String> eachItem();
}
"@ThroughField: '...' declares focus 'Integer' over field 'items' of type List<String>, whose elements the standard traversal hands back as 'String'"
The method's declared focus does not contain what the auto-detected traversal hands back. That is the container's elements, a Map's values, an Optional's element, or Object where the element sits behind a super- or unbounded wildcard.
Fix. Declare the focus as the type the message names, or name a traversal of your own with @ThroughField(field = "items", traversal = "..."), which is the author's undertaking that it rebuilds the declared shape.
Why
Why
A focus that does not contain that type could only compile through a cast, throwing ClassCastException on the caller's first getAll or modify where it narrows, and letting ill-typed writes into the container where it widens. Containment, not sameness: a wildcard focus over the element (? extends CharSequence over CharSequence elements, or its element's supertype bound) stays accepted, and an extends-wildcard element is held to its bound (List<? extends CharSequence> hands back CharSequence).
A declaration that produces it
A declaration that produces it
final class Shelf {
public List<String> items() {
return List.of();
}
public Shelf withItems(List<String> items) {
return this;
}
}
@ImportOptics
interface ShelfOpticsSpec extends OpticsSpec<Shelf> {
@Wither("withItems")
Lens<Shelf, List<String>> items();
@ThroughField(field = "items")
Traversal<Shelf, Integer> eachItem();
}
"@InstanceOf target 'com.example.Foo' is not a subtype of source type 'com.example.Base'"
The class passed to @InstanceOf(SubType.class) is not a subclass of the optic's source type.
Fix. Verify that SubType extends or implements the spec's <S> parameter. If you are working with sum types that don't use a sealed hierarchy (such as Jackson's pre-3.x JsonNode), use @MatchWhen with predicate and getter method names instead.
A declaration that produces it
A declaration that produces it
sealed interface Payment permits Card, Cash {}
record Card(String number) implements Payment {}
record Cash(int pence) implements Payment {}
@ImportOptics
interface PaymentOpticsSpec extends OpticsSpec<Payment> {
@InstanceOf(String.class)
Prism<Payment, String> text();
}
"@InstanceOf: '...' declares its focus as Circle<T>, which the test cannot narrow to"
The prism promises a type argument the test cannot check.
Fix. Declare the focus as Circle<?>, which is what the test earns, or narrow through a predicate and getter of the source type with @MatchWhen, which reads the argument off the source rather than inventing it. Where the source type does carry the argument, as in a Circle<X> implements Shape<X> reached from Shape<T>, the prism may promise it and the generated test names it. See Spec Interfaces.
Why
Why
@InstanceOf takes a class constant, which is raw, and the generated instanceof runs after erasure, so the only arguments the narrowed value is known to have are the ones the source type pins down. class Circle<X> extends Shape reached from a Shape that declares no parameters pins none: every instantiation passes the same test, and a Prism<Shape, Circle<T>> would hand any of them back as the T the caller asked for, to fail on the first read.
A declaration that produces it
A declaration that produces it
class Shape {}
class Circle<X> extends Shape {}
@ImportOptics
interface ShapeOpticsSpec<T> extends OpticsSpec<Shape> {
@InstanceOf(Circle.class)
Prism<Shape, Circle<T>> circle();
}
"@InstanceOf: '...' names '...', which carries type parameters of its own and is a member of a generic type"
The test has to name the type it checks, and an instanceof cannot write Outer<X>.Inner<Y>. Naming Inner's type arguments would mean naming the enclosing type's as well, which instanceof does not allow.
Fix. Declare the member static, so it can be named on its own, or narrow through a predicate and getter with @MatchWhen.
Why
Why
The remaining Outer.Inner is raw: it checks nothing about Y, and it is a rawtypes warning in the consuming build besides. A member of a non-generic type is unaffected, since Outer.Inner<Y> names itself perfectly well.
A declaration that produces it
A declaration that produces it
class Node<U> {}
class Outer<X> {
class Inner<Y> extends Node<Y> {}
}
@ImportOptics
interface NodeOpticsSpec<U> extends OpticsSpec<Node<U>> {
@InstanceOf(Outer.Inner.class)
Prism<Node<U>, Outer<?>.Inner<U>> inner();
}
"@InstanceOf: '...' narrows to '...', which is not a '...'"
The class the annotation names is not one the prism's focus type accepts.
Fix. Name the class the focus declares, or declare the focus as a supertype of the narrowed type. A prism whose focus is deliberately wider than the test is fine, as in @InstanceOf(ArrayList.class) Prism<Collection<T>, List<T>>; it is only a focus the narrowed value cannot be assigned to that is rejected.
Why
Why
Either the two are unrelated, or the source type pins the target's argument to something the focus does not agree with: OpticsSpec<Node<String>> narrowed to Leaf can only be a Leaf<String>, whatever a Prism<Node<String>, Leaf<U>> says.
A declaration that produces it
A declaration that produces it
sealed interface Payment permits Card, Cash {}
record Card(String number) implements Payment {}
record Cash(int pence) implements Payment {}
@ImportOptics
interface PaymentOpticsSpec extends OpticsSpec<Payment> {
@InstanceOf(Card.class)
Prism<Payment, Cash> card();
}
"@ViaCopyAndSet: copyConstructor names '...', which does not resolve to a type"
copyConstructor is a plain string, resolved as a fully qualified class name only: it is not read against the spec interface's imports, and it takes no type arguments.
Fix. Give the class's fully qualified name (com.example.BaseConfig; a nested class is com.example.Outer.Base), the class alone without type arguments, since the processor supplies those from the source type's own extends clause. Drop the attribute to pass the source unchanged.
A declaration that produces it
A declaration that produces it
// Endpoint is the legacy type from the copy-strategies page: two copy constructors,
// taking BaseEndpoint and Audited, and a setHost setter.
@ImportOptics
interface EndpointOpticsSpec extends OpticsSpec<Endpoint> {
@ViaCopyAndSet(copyConstructor = "com.example.MissingBase", setter = "setHost")
Lens<Endpoint, String> host();
}
"@ViaCopyAndSet: copyConstructor names '...', which 'S' does not extend or implement"
The generated setter passes the source to the copy constructor as (ParameterType) source, so only a supertype of S can be named there.
Fix. Name a class or interface S extends or implements, or drop the attribute.
A declaration that produces it
A declaration that produces it
@ImportOptics
interface EndpointOpticsSpec extends OpticsSpec<Endpoint> {
@ViaCopyAndSet(copyConstructor = "java.lang.Thread", setter = "setHost")
Lens<Endpoint, String> host();
}
"@ViaCopyAndSet: copyConstructor names '...', which is not public and so cannot be named from '...'"
The generated optics class has to write the cast, so it has to be able to name the type. A package-private supertype is invisible from the package the optics class is generated into, even though new S(source), which never names it, would have compiled.
Fix. Name a public supertype, generate into that package with @ImportOptics(targetPackage = ...), or drop the attribute.
"@ViaCopyAndSet: copyConstructor names '...', which '...' reaches as '...', and no constructor accepts"
The name is a genuine supertype, but no single-argument constructor of S takes the type S actually reaches it as. java.lang.Object and marker interfaces such as Serializable reach this often.
Fix. Name a supertype of S that one of the listed constructors takes, as the class alone without type arguments, or drop the attribute. The list carries type arguments and the attribute does not, so read it to recognise your supertype in it rather than to copy from it, and a listed type that is not a supertype of S cannot be named at all. The attribute is only needed when the copy constructor is overloaded; see Copy Strategies.
Why
Why
The message names both the type you gave and the one S reaches, which differ when S's own extends clause pins the arguments: class PNode<X> extends PBase<String> reaches PBase as PBase<String>, whatever X is.
A declaration that produces it
A declaration that produces it
@ImportOptics
interface EndpointOpticsSpec extends OpticsSpec<Endpoint> {
@ViaCopyAndSet(copyConstructor = "java.lang.Object", setter = "setHost")
Lens<Endpoint, String> host();
}
"@ViaCopyAndSet: '...' is written with a wildcard type argument"
The source type carries a wildcard, OpticsSpec<Node<?>>, and the strategy rebuilds it through a constructor.
Fix. Name the type the wildcard stands for, or switch to @Wither, which rebuilds through a method and names no constructor, so a wildcard source type is no obstacle there.
Why
Why
new Node<?>(...) is not something that can be written, whatever the arguments. @ViaConstructor reports the same thing for the same reason. An inner class draws the sibling message, because its constructor call needs an enclosing instance the generated class has no way to reach.
A declaration that produces it
A declaration that produces it
final class Slot<T> {
private String label = "";
Slot() {}
Slot(Slot<T> other) {
this.label = other.label;
}
public String label() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
}
@ImportOptics
interface SlotOpticsSpec extends OpticsSpec<Slot<?>> {
@ViaCopyAndSet(setter = "setLabel")
Lens<Slot<?>, String> label();
}
"@ImportOptics: '...' focuses '...', which is not a '...'"
A prism runs both ways, and the generated one builds back with identity: it returns the value it narrowed.
Fix. Focus the variant that carries the value, TextNode rather than String, and read the payload with a further optic. Where the value type is the point, write that prism by hand with Prism.of and a build side that constructs the source, such as TextNode::valueOf.
Why
Why
That is only a source when the focus is one, so a focus that is a value rather than a variant has no build side the processor could write: Prism<JsonNode, String> would need to rebuild a JsonNode from a bare String, and nothing in the declaration says how. The requirement belongs to the prism rather than to either hint, so @InstanceOf and @MatchWhen are both held to it. That includes an @InstanceOf whose narrowing is sound but reaches the focus through a supertype the source does not share, Prism<Base, Marker> for a Sub implements Base, Marker.
A declaration that produces it
A declaration that produces it
sealed interface Payment permits Card, Cash {
default boolean isCard() {
return this instanceof Card;
}
default String number() {
return "";
}
}
record Card(String number) implements Payment {}
record Cash(int pence) implements Payment {}
@ImportOptics
interface PaymentOpticsSpec extends OpticsSpec<Payment> {
@MatchWhen(predicate = "isCard", getter = "number")
Prism<Payment, String> number();
}
"cannot find symbol", inside the generated XPrisms.java, after using @MatchWhen
The processor does not validate the strings in @MatchWhen(predicate = "isFoo", getter = "asFoo"). It splices them into the generated source verbatim, so a typo surfaces as an ordinary javac error inside generated code rather than as a processor message.
Fix. Check the names against the source type's API. Both methods must take no arguments; the predicate returns boolean and the getter returns the prism's target type.
A declaration that produces it
A declaration that produces it
sealed interface Payment permits Card, Cash {}
record Card(String number) implements Payment {}
record Cash(int pence) implements Payment {}
@ImportOptics
interface PaymentOpticsSpec extends OpticsSpec<Payment> {
@MatchWhen(predicate = "isCrad", getter = "asCard")
Prism<Payment, Card> card();
}
"Prism method 'x' requires a prism hint annotation: @InstanceOf or @MatchWhen"
A spec-interface method returning Prism<S, A> with neither hint.
Fix. Add @InstanceOf for a real subtype, or @MatchWhen for a check-and-extract API. The same rule applies to traversals: "Traversal method 'x' requires a traversal hint annotation: @TraverseWith or @ThroughField".
A declaration that produces it
A declaration that produces it
sealed interface Payment permits Card, Cash {}
record Card(String number) implements Payment {}
record Cash(int pence) implements Payment {}
@ImportOptics
interface PaymentOpticsSpec extends OpticsSpec<Payment> {
Prism<Payment, Card> card();
}
@GeneratePathBridge and @PathVia
Every message on this page is quoted as the processor emits it, with 'x' standing in for the name it prints.
The bridge is a file you never wrote and cannot edit, so the errors below refuse a shape at your own declaration rather than emitting source that would fail, or warn, in the build that consumes it. The last entry is a warning rather than an error: the bridge it describes is written, it just has nothing in it.
"@PathVia: the return type of 'x' is 'Y', which no Path wraps"
The method returns a type the bridge has no Path for. The bridged set is Optional, Maybe, Either, Try, Validated and IO; CompletableFuture is the type most often met outside it.
Fix. Return one of the six, or drop @PathVia and wrap the call by hand.
A declaration that produces it
A declaration that produces it
@GeneratePathBridge
interface Orders {
@PathVia
CompletableFuture<String> find(String id);
}
"@PathVia: the signature of 'x' names the raw type 'Y'"
A generic type is written without its arguments somewhere the bridge copies verbatim: Optional as the return type, Optional<List> as its argument, List as a parameter. Each becomes a [rawtypes] warning in the generated file, and the @SuppressWarnings on your own declaration does not cover a file it does not appear in.
Fix. Name the type arguments: Optional<Item> rather than Optional.
A declaration that produces it
A declaration that produces it
@GeneratePathBridge
interface Orders {
@PathVia
Optional find(String id);
}
"@PathVia: the error type of the 'Validated' returned by 'x' is the wildcard '?'"
A Validated bridge names its error type twice: in the ValidationPath it returns, and in the Semigroup it asks the caller for.
Fix. Name the error type.
Why
Why
A wildcard is a different captured type at each mention, so no argument satisfies both.
Only the error position is affected. Validated<String, ? extends Number> is fine, Validated<List<? extends CharSequence>, String> is fine because the wildcard is nested and denotes one type at both mentions, and so are wildcards in Optional, Maybe, Either and Try returns.
A declaration that produces it
A declaration that produces it
@GeneratePathBridge
interface Orders {
@PathVia
Validated<?, String> find(String id);
}
"@PathVia: the type parameter 'T' on 'x' has the same name as 'Y's"
The bridge declares the interface's type parameters and the method's side by side, which the delegate never does; where the names collide, the method's hides the interface's.
Fix. Rename the method's type parameter.
Why
Why
An inherited <T extends U> on a Derived<T> would be written <T extends T>, and a parameter typed by the interface's T would silently become the method's.
Only a collision the signature actually depends on is refused. <T> Optional<T> get(T t) on a Derived<T> names nothing it hides, and is generated unchanged.
A declaration that produces it
A declaration that produces it
interface Narrower<U> {
@PathVia
<T extends U> Optional<T> narrow(T candidate);
}
@GeneratePathBridge
interface TextNarrower<T> extends Narrower<T> {}
"@PathVia: the bridge cannot call 'x'"
The method is static or private. The bridge reaches its delegate through an interface reference, which gets at abstract and default members and nothing else.
Fix. Make it an abstract or default instance method, or drop @PathVia from it.
A declaration that produces it
A declaration that produces it
@GeneratePathBridge
interface Orders {
@PathVia
static Optional<String> find(String id) {
return Optional.empty();
}
}
"@PathVia: the bridge signature for 'x' is already taken"
Two @PathVia methods land on the same generated name and parameter types, usually through @PathVia(name = ...). One class cannot declare both.
Fix. Give one of them a distinct name, or drop @PathVia from it.
A declaration that produces it
A declaration that produces it
@GeneratePathBridge
interface Orders {
@PathVia
Optional<String> find(String id);
@PathVia(name = "find")
Optional<String> lookup(String id);
}
"@PathVia: @PathVia(name = "...") is not a method name"
The name attribute is not a Java identifier, or it is a keyword. The bridge declares a method called exactly that.
Fix. Give a plain identifier, or drop the attribute to keep the delegate's own name.
A declaration that produces it
A declaration that produces it
@GeneratePathBridge
interface Orders {
@PathVia(name = "find-order")
Optional<String> find(String id);
}
"@GeneratePathBridge: on 'X', the signature names 'Y', which cannot be reached from 'p'"
targetPackage puts the bridge in package p, and something the bridge writes down, a parameter type, a return type, a bound or the delegate itself, is not visible there. The same message names the bound on 'T' when the culprit is a type parameter's bound.
Fix. Make the type public, or drop targetPackage so the bridge is written beside the interface.
A declaration that produces it
A declaration that produces it
class Secret {}
@GeneratePathBridge(targetPackage = "com.example.paths")
interface Vault {
@PathVia
Optional<Secret> find(String id);
}
"@GeneratePathBridge: no @PathVia method was found among 'X's members" (a warning)
No @PathVia method survives among the interface's members, so the bridge is written with a constructor and nothing else.
Fix. Put @PathVia on the methods to bridge, or drop @GeneratePathBridge.
Why
Why
Usually that means none was ever written; it can also mean one was hidden, which the note below covers.
Inherited methods do count: a bridge for StringStore extends Store<String> picks up Store's, read under String. But @PathVia is not inherited by an override, so a method that overrides an annotated one hides it unless it is annotated too, and that is the usual cause of this message on an interface whose parent is annotated.
A processor warning cannot be suppressed, so a build running -Werror treats this as an error.
A declaration that draws it
A declaration that draws it
@GeneratePathBridge
interface Orders {
Optional<String> find(String id);
}
Focus DSL chains
traverseOver and the higher-kinded witness type
traverseOver is generic in the higher-kinded witness type.
Fix. State the type parameters explicitly when the witness is not obvious from context:
TraversalPath<User, Role> allRoles =
rolesPath.<ListKind.Witness, Role>traverseOver(ListTraverse.INSTANCE);
Why
Why
This is the same phantom-type-parameter family as Effect §1: on the supported compiler javac usually resolves the witness from context rather than emitting a hard cannot infer type arguments error. The reliable failure mode is not a guaranteed compile error but ambiguity in long Focus chains, where the witness should be stated explicitly for clarity and to avoid Object leaking in.
"Incompatible types when chaining .each().via()"
Usually one .each() too many.
Fix. Drop the extra .each(), and break long chains into intermediate variables so each carries a concrete type:
TraversalPath<Company, Department> depts = CompanyFocus.departments();
TraversalPath<Company, Employee> employees = depts.via(DepartmentFocus.employees());
TraversalPath<Company, Integer> salaries = employees.via(EmployeeFocus.salary());
Why
Why
A generated accessor for a collection component is already element-level, so CompanyFocus.departments() is a TraversalPath<Company, Department> and adding .each() steps into a Department as though it were a list. Long chains can also overflow Java's inference budget.
each() is <E> TraversalPath<S, E> and infers E from the assignment target, so a surplus hop type-checks and then fails at runtime when the list traversal is applied to something that is not a list. It is not caught by the compiler, which is why it belongs on this page rather than in a debugging note.
"Cannot infer type argument(s)" on an intermediate .each()
Only the final each() in a chain can infer its element type from the target type. An intermediate one has nothing to infer from.
Fix. Spell the element type at the intermediate hop:
TraversalPath<Company, Integer> allSalaries =
FocusPath.of(CompanyLenses.departments())
.<Department>each()
.via(DepartmentLenses.employees())
.<Employee>each()
.via(EmployeeLenses.salary());
"Method reference ::new doesn't work with single-field records as BiFunction"
A single-component record has no two-argument constructor, and Lens.of's setter is a BiFunction<S, A, S> taking (source, newValue). It is an arity mismatch, not an inference wobble.
Fix. Use an explicit lambda:
Lens<Outer, Inner> lens = Lens.of(Outer::inner, (o, i) -> new Outer(i));
"Sealed or non-sealed local classes are not allowed"
Defining a sealed interface inside a method body. Java does not permit this regardless of HKJ.
Fix. Hoist the sealed interface to class or top level.
Free Monad DSL programs
"Cannot resolve method 'flatMap(Function<...>)'"
The Free<F, A> value's witness type does not match what the surrounding interpreter expects, or you are mixing Free<OpticOpKind.Witness, ...> with another Free instance.
Fix. Confirm that every step in the program uses the same OpticPrograms factory methods, and that interpreter calls are paired with the matching witness.
"Type mismatch: Free<F, A> cannot be converted to A"
Forgetting to call an interpreter. A Free program is data; you must run it to get a result.
Fix. Pass the program to an interpreter:
Person result = OpticInterpreters.direct().run(program);
When the message does not match anything here
- Is the project rebuilt from clean? Many "cannot find symbol" errors clear after
./gradlew clean build. - Is the annotation processor on the classpath? See Build Plugins for the canonical setup.
- Is the IDE indexing the generated sources directory? Refresh the project after a build.
- If it is none of these, please file an issue at the Higher-Kinded-J GitHub repository with the minimal reproducer and the full error.
- "cannot find symbol: XLenses" is almost always a build problem, not a code problem: the processor did not run, or the IDE has not indexed the generated sources.
- A note is not a failure. An error always stops the build, a warning stops it only under
-Werror, and a note stops nothing. The heading says which whenever it is not an error. - The annotations are shape-specific.
@GenerateLenseswants a record,@GeneratePrismswants a sealed interface or enum, and using one on the other is rejected at the declaration. - A spec interface needs a copy strategy for every lens method, because the processor has no way to guess how your external type rebuilds itself.
- Most Focus DSL errors are one hop too many, or one witness too few. A generated collection accessor is already element-level, so an extra
.each()is the common cause; and only the finaleach()in a chain can infer its element type. - Read the processor's own message first. It names the element it rejected, which is faster than working backwards from the downstream "cannot find symbol".
- Annotations at a Glance: which annotation to reach for, and what it generates
- Optics for External Types: the
@ImportOpticsand spec-interface rules these errors enforce - Build Plugins: the canonical processor setup
Previous: Conversions Next: Production Readiness