Common Compiler Errors
Transformer code is generic-heavy by nature: a typical signature like Kind<EitherTKind.Witness<CompletableFutureKind.Witness, DomainError>, Result> packs three type parameters into two layers. When something goes wrong, the messages can be hard to read. Find your symptom in the table below and follow it to its section.
- Which transformer mistakes javac catches, which the HKJ checker adds a message to, and which nothing catches
- How to satisfy the missing-Monad constraint when constructing a transformer monad
- How to unify error types across an
EitherTchain - When to call
.value()and when to leave a transformer alone
The compiler is mostly on your side here, and that is not an accident. A transformer stack encodes the error type inside the witness itself (EitherTKind.Witness<F, L>), so a step with the wrong L is a genuinely different type. The Effect Path API gets the same guarantee from the family parameter on its capability interfaces: compare Effect §5.
- §1, §2, §4 and §5 are javac errors. The build stops, and the message is quoted in the section.
- §1 and §4 also carry an HKJ checker companion, which adds an actionable message beside javac's own. §2 and §5 javac catches on its own. See Compile-Time Checks.
- §3 compiles, and nothing reports it, the checker included.
Lresolves toObjectand stays there.
Find your message
| The symptom | What it is | Caught by |
|---|---|---|
method eitherT cannot be applied, expecting Monad<F> | The outer monad was not passed to the factory | javac, plus transformer-missing-monad |
incompatible types, with two EitherTKind.Witness shapes | Two different error types L in one chain | javac |
Either.right silently gets L = Object | Nothing constrains L, so javac defaults it | Nothing. Add the witness yourself |
cannot find symbol .value() on a Kind | .value() is on the concrete transformer, not on Kind | javac, plus kind-value-narrow |
For.from not applicable, naming two stack types | Mixed transformer stacks in one comprehension | javac |
Which line of defence answers depends on the mistake:
flowchart TD
C["Your transformer stack"]
J{"Does javac<br/>reject it?"}
N["Nothing says a word<br/>§3, L becomes Object"]
K{"Does an HKJ check<br/>run beside it?"}
KE["javac's error, plus an<br/>actionable HKJ message<br/>§1, §4"]
JO["javac's error alone<br/>§2, §5"]
C --> J
J -->|no| N
J -->|yes| K
K -->|yes| KE
K -->|no| JO
classDef step fill:#8caaee,stroke:#1e66f5,color:#232634
classDef decision fill:#e5c890,stroke:#df8e1d,color:#232634
classDef caught fill:#a6d189,stroke:#40a02b,color:#232634
classDef missed fill:#e78284,stroke:#d20f39,color:#232634
class C step
class J,K decision
class KE,JO caught
class N missed
1. "Method eitherT cannot be applied to given types"
A javac error. Every transformer monad needs a Monad<F> instance for the outer effect, and the factory cannot infer it from thin air.
The error:
error: method eitherT in class Instances cannot be applied to given types;
var eitherTMonad = Instances.eitherT();
^
required: Monad<F>
found: no arguments
reason: cannot infer type-variable(s) F,L
(actual and formal argument lists differ in length)
The trigger:
var eitherTMonad =
Instances.eitherT(); // missing argument
The fix: pass the outer monad to the factory.
var futureMonad = Instances.monadError(completableFuture());
var eitherTMonad =
Instances.eitherT(futureMonad);
The same rule applies to OptionalTMonad, MaybeTMonad, ReaderTMonad, StateTMonad, and WriterTMonad. WriterTMonad additionally requires a Monoid<W> for the output type.
The transformer-missing-monad check is the companion to javac's own error, and names the required outer Monad<F>, plus the Monoid<W> for WriterTMonad. See Compile-Time Checks.
2. "Incompatible types: error type mismatch in EitherT chain"
A javac error. Every step in an EitherT comprehension shares one error type L. Mixing a step that fails with String and one that fails with DomainError will not compile.
The error:
error: incompatible types: cannot infer type-variable(s) B
(argument mismatch; bad return type in lambda expression
EitherT<F,String,User> cannot be converted to Kind<EitherTKind.Witness<F,DomainError>,B>)
The trigger:
// lookupUser returns EitherT<F, String, User>: error type is String, not DomainError
EitherT<CompletableFutureKind.Witness, String, User> lookupUser(String id) {
return EitherT.fromEither(futureMonad, Either.<String, User>right(new User(id)));
}
// eitherTMonad is a MonadError<EitherTKind.Witness<F, DomainError>, DomainError>
For.from(eitherTMonad, validatedET)
.from(id -> lookupUser(id)); // String vs DomainError mismatch
The fix: unify the error type, either by changing the function or by mapping at the boundary.
// Option 1: change lookupUser to use DomainError
EitherT<CompletableFutureKind.Witness, DomainError, User> lookupUser(String id) {
return EitherT.fromEither(futureMonad, Either.<DomainError, User>right(new User(id)));
}
// Option 2: lift the foreign error type at the call site
EitherT<CompletableFutureKind.Witness, String, User> lookupUser(String id) {
return EitherT.fromEither(futureMonad, Either.<String, User>right(new User(id)));
}
EitherT<CompletableFutureKind.Witness, DomainError, User> liftLookup(String id) {
var raw = lookupUser(id); // EitherT<F, String, User>
return EitherT.fromKind(
futureMonad.map(
either -> either.mapLeft(DomainError.UserLookup::new), // String -> DomainError
raw.value()));
}
Option 1 is preferred for new code. Option 2 is useful when integrating with code you cannot change.
Why this one is caught
Why this one is caught
The transformer stack encodes L inside the witness itself (EitherTKind.Witness<F, L>), so a step with a different L is a genuinely different Kind<…> type, and For.from(...).from(...) fails to type-check.
An Effect Path chain is checked the same way, through the family its capability interfaces carry, as Effect §5 shows. One difference: an EitherPath step may fail with a subtype of the chain's error, while a transformer's L must match exactly.
3. The phantom L on EitherT.fromEither / Either.right
This compiles, and nothing reports it. Either.right(value) has no Left, so when nothing constrains L, javac resolves it to java.lang.Object and says nothing.
The trigger:
// L bound to DomainError by the typed variable: fine
EitherT<CompletableFutureKind.Witness, DomainError, ValidatedOrder> step =
EitherT.fromEither(futureMonad, Either.right(validated));
// nothing constrains L, so L = Object, and it compiles
var untypedStep = EitherT.fromEither(futureMonad, Either.right(validated));
The fix: pin L with an explicit type witness on the Either, so Object never leaks in:
EitherT.fromEither(futureMonad, Either.<DomainError, ValidatedOrder>right(validated));
The witness costs nothing at runtime and keeps the error type honest.
Why it is silent, and what older write-ups said
Why it is silent, and what older write-ups said
Older write-ups described an error here:
error: method fromEither ... cannot be applied to given types;
reason: cannot infer type-variable(s) F, L, R
On the supported compiler this does not happen. It is the same phantom-type-parameter situation as Effect §1: modern javac resolves L to java.lang.Object and the code compiles rather than erroring. The mistake is silent, not loud.
The HKJ checker does not cover it either. This is the excluded inference family: javac raises nothing, so there is no error for a companion check to annotate, and an unconstrained L is not reliably distinguishable from code that means Either<Object, …>. The witness is the fix. See Compile-Time Checks for what the checker does and does not cover.
4. "Cannot find symbol .value()" on a Kind
A javac error. .value() is defined on the concrete EitherT<F, L, R>, and equivalently on OptionalT and MaybeT. ReaderT and WriterT expose run() instead, and StateT exposes runStateT(state). None of them is part of the Kind interface, so a Kind value has to be narrowed back to the concrete transformer first.
The error:
error: cannot find symbol
var future = workflow.value();
^
symbol: method value()
location: variable workflow of type Kind<EitherTKind.Witness<...>, Result>
The trigger:
Kind<EitherTKind.Witness<CompletableFutureKind.Witness, DomainError>, Result> workflow =
eitherTMonad.flatMap(id -> fetchEither(id), validatedET);
var result = workflow.value(); // Kind has no value() method
The fix: call the matching narrow helper before extracting the underlying value.
import static org.higherkindedj.hkt.either_t.EitherTKindHelper.EITHER_T;
Kind<EitherTKind.Witness<CompletableFutureKind.Witness, DomainError>, Result> workflow =
eitherTMonad.flatMap(id -> fetchEither(id), validatedET);
var result = EITHER_T.narrow(workflow).value();
This boundary conversion is unavoidable when a method is typed in Kind but you need a concrete operation. If you control the call site, declaring the variable as the concrete EitherT<...> removes the need:
EitherT<CompletableFutureKind.Witness, DomainError, Result> workflow =
EITHER_T.narrow(eitherTMonad.flatMap(id -> fetchEither(id), validatedET));
var result = workflow.value();
The kind-value-narrow check is the companion to javac's own error, and points at the concrete-transformer narrow. See Compile-Time Checks.
5. "Method For.from is not applicable" with the wrong monad
A javac error. For.from(monad, source) requires the source's witness type to match the monad's. Passing an EitherTMonad and an OptionalT mixes two different transformer stacks.
The error:
error: no suitable method found for from(MonadError<EitherTKind.Witness<F,DomainError>,DomainError>,
OptionalT<F,User>)
method For.<M,A>from(Monad<M>,Kind<M,A>) is not applicable
(inference variable M has incompatible equality constraints
OptionalTKind.Witness<F>, EitherTKind.Witness<F,DomainError>)
The trigger:
// eitherTMonad is a MonadError<EitherTKind.Witness<F, DomainError>, DomainError>
For.from(eitherTMonad, OptionalT.fromKind(future)) // mismatched witness types
.yield(user -> user);
The fix: make sure the monad you pass to For.from matches the witness of every step.
// Either use EitherT throughout:
For.from(eitherTMonad, validatedET)
.from(id -> fetchEither(id))
.yield((id, result) -> result);
// Or build the matching OptionalTMonad and use OptionalT throughout:
var optionalTMonad = Instances.optionalT(futureMonad);
For.from(optionalTMonad, OptionalT.fromKind(future))
.yield(user -> user.id());
If you genuinely need to combine two effect layers, typed errors and absence in the same workflow, you are in stacking territory. See Tutorial 03: Stacking Transformers and Stack Archetypes.
- The witness is what makes these errors loud. A transformer stack carries
Lin the type, so a mismatched error type is a compile error rather than a runtime surprise. - Every transformer factory wants the outer monad.
Instances.eitherT(futureMonad), and aMonoid<W>as well forWriterTMonad. .value()lives on the concrete transformer, not onKind. Narrow first, or declare the variable concrete and skip the round trip.- Only §3 is silent. An unconstrained
LbecomesObjectwith nothing to warn you, so pin it withEither.<E, A>right(...)at the source.
- Path or Transformer?: the decision page that may save you from this chapter entirely
- Transformers at a Glance: reference card for every transformer
- Effect Path Common Compiler Errors: the equivalent for the Path API
- Compile-Time Checks: the checks that add a message beside javac's own
- Migration Cookbook: side-by-side translations between styles
Previous: Axes of Transformer Transformation Next: Capstone: A Multi-Capability Workflow