Errors Reference¶
Every error and warning Kraft prints during compilation has a stable code. This page lists each code with its cause and fix.
Reading a Kraft diagnostic¶
A Kraft diagnostic looks like this:
----------------------------------------
Kraft KSP Warning
----------------------------------------
[KRAFT-W001] <what went wrong>
<details: the types and properties involved, and how to fix it>
----------------------------------------
See: https://blu3berry-why.github.io/Kraft/user-guide/errors/#kraft-w001
- The code in square brackets on the first line identifies the diagnostic.
KRAFT-E###codes are errors, which fail compilation.KRAFT-W###codes are warnings, which do not. - The
See:line on the last line links to that code's entry below. - A code keeps its meaning across releases. The message wording can change, but the code stays the same, so it is safe to search for a code in logs, issues or CI output.
Suppressing a warning¶
A warning can be silenced on the declaration it points at, using Kotlin's standard @Suppress with the code:
@Suppress("KRAFT-W001")
fun List<RowDto>?.toRows(): List<Row> = this.orEmpty().filter { it.visible }.map { it.toRow() }
- The code must match exactly, including case:
@Suppress("kraft-w001")does not suppress anything. - A suppression only covers the code it names.
@Suppress("KRAFT-W002")does not silenceKRAFT-W001. - The annotation goes on the declaration itself. A
@Suppresson the enclosing class or file is not read. The one exception isKRAFT-W009, which is also read from the mapping declaration (the@MapConfigobject, or the@MapFrom/@MapToclass), so one annotation covers every property of that mapper. - Errors cannot be suppressed: they mean Kraft cannot generate valid code.
Errors¶
Examples omit imports. The annotations live in com.blu3berry.kraft.config and com.blu3berry.kraft.mapping.
KRAFT-E001 — Annotation on the wrong target¶
Severity: error
Cause: A Kraft annotation is placed on a declaration kind it does not support, for example @MapFrom or @MapTo on an object or interface.
data class Source(val value: Int)
@MapFrom(Source::class) // error: @MapFrom is only valid on class declarations
object NotAClass
Fix:
- Move the annotation onto a class, for example
@MapFrom(Source::class) data class Target(...). - For a
@MapConfigor@MapEnum, put it on anobject.
KRAFT-E002 — Missing annotation argument¶
Severity: error
Cause: A required argument of a Kraft annotation could not be read from the annotation. Valid Kotlin source cannot normally produce this, so it points at a processor and annotations version mismatch or a broken classpath.
Fix:
- Provide the missing argument. The message shows an example that matches the argument's kind:
@MapConfig(source = MyType::class)for a class,@MapUsing(target = "propertyName")for a string, or the enum's first entry such asConverterDirection.AUTO. - Arguments that have a default (such as
@MapUsing'ssourceanddirection) are never reported here; Kraft applies the declared default when the compiler leaves them out. - Make sure the Kraft annotations artifact and the Kraft KSP processor are the same version.
KRAFT-E003 — Argument is not a class literal¶
Severity: error
Cause: An annotation argument that must be a class literal (KClass<*>) has a different type. Valid Kotlin source cannot normally produce this, so it points at a processor and annotations version mismatch.
Fix:
- Use a class literal like
MyType::class. - Make sure the Kraft annotations artifact and the Kraft KSP processor are the same version.
KRAFT-E004 — Required property has no mapping source¶
Severity: error
Cause: A non-nullable target property has no default value and no source property to map from.
data class User(val name: String)
@MapFrom(User::class)
data class UserDto(
val name: String,
val age: Int, // error: no 'age' in User, not nullable, no default
)
Fix:
- Add
@MapField("sourceName")to the target property. - Or add a config override:
@FieldMapping(source = "sourceName", target = "age"). - Or set it from the mapper object:
@MapConstant val age = ...(see Constant Values). - Or make the property nullable (Kraft then passes null and warns
KRAFT-W009). - Or provide a default value in the target constructor.
KRAFT-E005 — Property type mismatch¶
Severity: error
Cause: A source property and the target property it maps to have different types, and no converter bridges them.
data class User(val id: Int)
@MapFrom(User::class)
data class UserDto(val id: String) // error: Int -> String has no converter
Fix:
- Register a
@KraftConverterfor the pair:@KraftConverter fun Int.toString(): String = .... - Or, if both types are enums, add
@MapEnum(source = A::class, target = B::class). - Or override just this property with
@MapUsingon the@MapConfigobject. - Or align the declared types, including nullability.
- When only nullability differs (
String?toString), make the target nullable, give it a default and add@MapIgnore, or choose the fallback with a whole-source@MapUsing(target = ...).
Map values that are nullable¶
When two maps differ only because the source value is nullable (Map<String, ItemSource?> into Map<String, ItemDto>), the error adds the hint Map values cannot be dropped like list elements: removing a null value would also remove its key. A list would drop the null element with mapNotNull, but a map cannot drop a value without dropping its key. Make the target values nullable, or filter first with a whole-source @MapUsing.
Duplicated generated types¶
When the two types share a simple name, live under different enclosing classes (AuthResponse.User.Role and AuthMe200Response.Role) and have identical enum entries or identical primary-constructor parameter names, the error opens with Types differ only by enclosing class: .... That shape usually comes from a code generator (for example kmpgen) inlining the same $ref twice, so the real fix is in the generator. To bridge the pair anyway, use @MapEnum(source = ..., target = ...) for enums, or a @MapConfig or @KraftConverter for classes. The usual "How to fix" list still follows the hint. See API-first client design for how to structure generated clients.
KRAFT-E006 — Invalid @MapField override¶
Severity: error
Cause: @MapField names a source property that does not exist in the source type.
data class User(val fullName: String)
@MapFrom(User::class)
data class UserDto(
@MapField("fulName") // error: User has no 'fulName'
val name: String,
)
Fix:
- Correct the
@MapFieldname; the message lists the available source properties and close matches. - Or make sure the source class declares the referenced property.
KRAFT-E007 — Missing primary constructor¶
Severity: error
Cause: The target type of a mapping declares no primary constructor, so Kraft cannot build an instance.
data class User(val name: String)
@MapFrom(User::class)
class UserDto { // error: only a secondary constructor
constructor(name: String)
}
Fix:
- Add a primary constructor to the target type:
data class UserDto(val name: String).
KRAFT-E008 — Constructor parameter without property¶
Severity: error
Cause: A constructor parameter of the target type has no matching property, so Kraft cannot read its type.
data class User(val name: String)
@MapFrom(User::class)
class UserDto(name: String) // error: 'name' is a parameter, not a property
Fix:
- Declare the parameter as a property:
val nameorvar name.
KRAFT-E009 — Unsupported constructor parameter type¶
Severity: error
Cause: A constructor parameter of the target type has a type that is not a class declaration, such as a type parameter.
data class Source(val value: Int)
@MapConfig(source = Source::class, target = Target::class)
object SourceMapper
class Target<T>(val value: Int, val extra: T) // error: T is a type parameter, not a class
Fix:
- Use a concrete class type for the parameter.
- Or give the parameter a default value; an unsupported parameter with a default is skipped.
KRAFT-E010 — Constructor and property mismatch¶
Severity: error
Cause: A target constructor parameter was skipped as unsupported and has no default value, so the generated constructor call cannot line up with the properties. It is reported together with the error that explains the skipped parameter (for example KRAFT-E009).
data class Source(val value: Int)
@MapConfig(source = Source::class, target = Target::class)
object SourceMapper
class Target<T>(val value: Int, val extra: T) // error: KRAFT-E009 for 'extra', then this
Fix:
- Fix the error reported for the skipped parameter first.
- Ensure each constructor argument has a matching property and a supported type.
KRAFT-E011 — Unsupported source property type¶
Severity: error
Cause: A source property has a type that does not resolve to a concrete class declaration, such as a type parameter.
// No minimal input is known; the message names the property, its resolved type and
// the declaration kind.
Fix:
- Use a concrete type for the property in the source type.
- Or handle the property with a
@MapUsingconverter.
KRAFT-E012 — Unmapped enum entries¶
Severity: error
Cause: A source enum entry has no matching target entry, and no FieldMapping covers it.
enum class Status { ACTIVE, ARCHIVED }
enum class StatusDto { ACTIVE }
@MapEnum(source = Status::class, target = StatusDto::class) // error: ARCHIVED is unmapped
object StatusMapper
Fix:
- Add a
FieldMappingfor each unmapped entry:fieldMappings = [FieldMapping(source = "ARCHIVED", target = "ACTIVE")]. - Or rename the target entry to match the source name for automatic 1:1 mapping.
KRAFT-E013 — Ignored property has no default¶
Severity: error
Cause: @MapIgnore or @MapIgnoreField ignores a target property that has no default value, so the generated constructor call would not compile.
data class User(val name: String)
@MapFrom(User::class)
data class UserDto(
val name: String,
@MapIgnore val age: Int, // error: ignored but has no default
)
Fix:
- Add a default value to the property in the target constructor.
- Or remove the ignore declaration.
KRAFT-E014 — Nullable nested source¶
Severity: error
Cause: A nullable nested source property maps to a non-null nested target property, so the generated nested call could fail at runtime.
data class Address(val city: String)
data class AddressDto(val city: String)
data class User(val address: Address?)
@MapFrom(User::class)
data class UserDto(val address: AddressDto) // error: nullable source, non-null target
Fix:
- Mark the target property as nullable.
- Or provide a
@MapUsingconverter that handles the null case.
KRAFT-E015 — Nested type not mappable¶
Severity: error
Cause: A nested mapping involves a type that is not a concrete class with a primary constructor, such as an interface, an abstract or sealed class, or a type parameter. For a collection property, the message names the element type that is not mappable.
interface Shape
data class Circle(val r: Int) : Shape
data class Source(val shape: Circle)
@MapFrom(Source::class)
data class Target(
@MapNested val shape: Shape, // error: Shape is an interface
)
Fix:
- Use a
@MapUsingconverter for this property. - Or make sure the collection's element type is a concrete mappable class.
- Or declare an explicit
@MapConfigwith a@NestedMappingfor the pair.
KRAFT-E016 — Ambiguous nested mapping¶
Severity: error
Cause: Several @NestedMapping declarations in a @MapConfig share the same target type, so Kraft cannot pick one.
@MapConfig(
source = Order::class,
target = OrderDto::class,
nestedMappings = [
NestedMapping(source = Home::class, target = AddressDto::class),
NestedMapping(source = Work::class, target = AddressDto::class), // error: same target twice
],
)
object OrderMapper
Fix:
- Remove duplicate
@NestedMappingentries so only one targets the type. - Or annotate the target property with
@MapNestedto resolve the ambiguity explicitly.
KRAFT-E017 — Ambiguous nested source property¶
Severity: error
Cause: Several source properties have the type an explicit @NestedMapping names as its source, so Kraft cannot pick the property.
data class Order(val billing: Address, val shipping: Address)
@MapConfig(
source = Order::class,
target = OrderDto::class,
nestedMappings = [NestedMapping(source = Address::class, target = AddressDto::class)], // error: two Address properties
)
object OrderMapper
Fix:
- Annotate the target property with
@MapNested(sourceName = "propertyName")to resolve the ambiguity explicitly.
KRAFT-E018 — Nested mapping source not found¶
Severity: error
Cause: An explicit @NestedMapping names a source type for which the source class has no property.
data class Order(val id: Int)
@MapConfig(
source = Order::class,
target = OrderDto::class,
nestedMappings = [NestedMapping(source = Address::class, target = AddressDto::class)], // error: Order has no Address property
)
object OrderMapper
Fix:
- Add a property of the nested source type to the source class.
- Or remove the
@NestedMappingdeclaration if it is no longer needed.
KRAFT-E019 — Duplicate mapping¶
Severity: error
Cause: Several @MapConfig objects declare the same source and target pair. Only one mapping per type pair is allowed.
@MapConfig(source = User::class, target = UserDto::class)
object FirstMapper
@MapConfig(source = User::class, target = UserDto::class) // error: pair already mapped by FirstMapper
object SecondMapper
Fix:
- Remove one of the
@MapConfigobjects, or merge their settings into one.
KRAFT-E020 — Circular nested mapping¶
Severity: error
Cause: Nested mappings reference each other in a cycle, which would generate mappers that call each other forever.
data class NodeSource(val value: Int, val next: NodeSource?)
data class NodeDto(val value: Int, val next: NodeDto?)
data class Container(val node: NodeSource)
@MapFrom(Container::class)
data class ContainerDto(val node: NodeDto) // error: NodeSource -> NodeDto nests itself
Fix:
- Break the cycle with a
@MapUsingconverter on one side.
KRAFT-E021 — Nested mapper could not be built¶
Severity: error
Cause: A mapper depends on a nested mapper that could not be built, so its generated code would not compile. It is reported together with the error that made the nested mapper fail.
data class Address(val city: String)
data class AddressDto(val city: String, val zip: String) // zip has no source: KRAFT-E004
data class Container(val address: Address)
@MapFrom(Container::class)
data class ContainerDto(val address: AddressDto) // error: the Address -> AddressDto mapper failed
Fix:
- Fix the error reported for the nested mapper first.
- Or map the property with a
@MapUsingconverter.
KRAFT-E022 — @MapIgnoreField property not found¶
Severity: error
Cause: @MapIgnoreField names a property that does not exist in the target constructor, for a TARGET or SOURCE direction.
@MapConfig(
source = User::class,
target = UserDto::class,
ignoredMappings = [MapIgnoreField("nonExistent", IgnoreSide.TARGET)], // error: UserDto has no 'nonExistent'
)
object UserMapper
Fix:
- Correct the property name; the message lists the available target properties.
- Or remove the
@MapIgnoreFielddeclaration.
KRAFT-E023 — Missing reverse converter¶
Severity: error
Cause: @MapReverse reverses a mapping that uses a forward @MapUsing converter, but no reverse converter is defined.
@MapConfig(source = Source::class, target = Target::class)
@MapReverse
object SourceMapper {
@MapUsing(source = "value", target = "text")
fun format(v: Int): String = v.toString() // error: no reverse converter for text -> value
}
Fix:
- Add a reverse
@MapUsingfunction in the same config object. - Or add
@MapIgnoreField("name", direction = IgnoreSide.SOURCE)to skip the property in reverse.
KRAFT-E024 — Config override of unknown property¶
Severity: error
Cause: A config-level FieldMapping names a source property that does not exist.
@MapConfig(
source = User::class,
target = UserDto::class,
fieldMappings = [FieldMapping(source = "ghost", target = "name")], // error: User has no 'ghost'
)
object UserMapper
Fix:
- Correct the source name in the
FieldMapping; the message lists the available source properties.
KRAFT-E025 — Multiple @MapUsing converters for one property¶
Severity: error
Cause: More than one @MapUsing converter targets the same property. Only one converter per target property is allowed.
@MapConfig(source = Src::class, target = Dst::class)
object SrcMapper {
@MapUsing(source = "value", target = "text")
fun convertFirst(v: Int): String = v.toString()
@MapUsing(source = "value", target = "text") // error: 'text' already has a converter
fun convertSecond(v: Int): String = "duplicate"
}
Fix:
- Keep one
@MapUsingconverter per target property and remove the others.
KRAFT-E026 — Unknown property in @MapUsing¶
Severity: error
Cause: @MapUsing names a source or target property that does not exist.
@MapConfig(source = Src::class, target = Dst::class)
object SrcMapper {
@MapUsing(source = "ghost", target = "text") // error: Src has no 'ghost'
fun convert(v: Int): String = v.toString()
}
Fix:
- Correct the property name; the message lists the available properties.
- Or add the missing property to the source or target class.
KRAFT-E027 — @MapNested source property not found¶
Severity: error
Cause: @MapNested resolves to a source property name that does not exist in the source type.
@MapFrom(User::class)
data class UserDto(
@MapNested(sourceName = "typo") // error: User has no 'typo'
val address: AddressDto,
)
Fix:
- Correct the
sourceName, or remove it to use the target property's own name. - Or add the property to the source class.
KRAFT-E028 — @MapFrom and @MapTo on one class¶
Severity: error
Cause: A class carries both @MapFrom and @MapTo. Only one mapping annotation is allowed per class.
@MapFrom(Source::class)
@MapTo(Other::class) // error: both @MapFrom and @MapTo
data class Both(val value: Int)
Fix:
- Keep one of the two annotations on the class.
- Declare the second direction on the other class, or use a
@MapConfigobject.
KRAFT-E029 — @MapReverse without a mapping annotation¶
Severity: error
Cause: @MapReverse is not paired with a mapping annotation (@MapFrom, @MapTo, @MapConfig or @MapEnum) on the same declaration.
Fix:
- Add
@MapFrom,@MapTo,@MapConfigor@MapEnumto the same declaration. - Or remove
@MapReverse.
KRAFT-E030 — Ambiguous classpath converter¶
Severity: error
Cause: Two dependency modules register a @KraftConverter for the same source and target pair, so Kraft cannot choose between them.
// module :upstream-a
@KraftConverter fun Int.toLabel(): String = "a$this"
// module :upstream-b
@KraftConverter fun Int.toLabel(): String = "b$this"
// module :app depends on both and maps an Int to a String // error: ambiguous
Fix:
- Add a same-module
@KraftConverterfor the pair; it takes precedence over dependencies. - Or use a per-property
@MapUsing.
KRAFT-E031 — @MapUsing REVERSE without @MapReverse¶
Severity: error
Cause: A @MapUsing(direction = REVERSE) converter sits in a @MapConfig that is not annotated with @MapReverse, so the converter would never be used.
@MapConfig(source = Src::class, target = Dst::class) // no @MapReverse
object SrcMapper {
@MapUsing(source = "text", target = "value", direction = ConverterDirection.REVERSE) // error: no effect
fun parse(s: String): Int = s.toInt()
}
Fix:
- Remove the
directionargument. - Or add
@MapReverseto the config object.
KRAFT-E032 — @MapUsing internal lookup failure¶
Severity: error
Cause: Kraft validated a @MapUsing property name and then could not find the property in its own lookup. This is an internal inconsistency, not a mistake in your code.
Fix:
- Report it on the Kraft issue tracker with the converter and type names from the message.
- Work around it by moving the property mapping to a
FieldMappingor a different@MapUsing.
KRAFT-E033 — @MapUsing without a target¶
Severity: error
Cause: @MapUsing has an empty target value, so Kraft does not know which property it converts.
@MapConfig(source = Src::class, target = Dst::class)
object SrcMapper {
@MapUsing(target = "") // error: empty target
fun convert(src: Src): String = ""
}
Fix:
- Set
targetto the name of the target property the function produces.
KRAFT-E034 — @MapUsing unknown direction¶
Severity: error
Cause: @MapUsing has a direction that names an enum entry the processor does not know. Valid source cannot normally produce this, so it points at a processor and annotations version mismatch.
Fix:
- Use one of the
ConverterDirectionvalues:AUTO,FORWARDorREVERSE. - Make sure the Kraft annotations artifact and the Kraft KSP processor are the same version.
KRAFT-E035 — Whole-source converter parameter mismatch¶
Severity: error
Cause: A whole-source @MapUsing converter (no source) takes a parameter whose type is not the source class of the mapping.
@MapConfig(source = Src::class, target = Dst::class)
object SrcMapper {
@MapUsing(target = "label")
fun format(other: Other): String = "" // error: parameter must be Src
}
Fix:
- Change the parameter (or extension receiver) type to the source class of the
@MapConfig. - Or name a source property with
@MapUsing(source = ...)for a single-property converter.
KRAFT-E036 — @MapUsing return type mismatch¶
Severity: error
Cause: The return type of a @MapUsing function does not match the type of the target property it fills.
data class Dst(val text: String)
@MapConfig(source = Src::class, target = Dst::class)
object SrcMapper {
@MapUsing(source = "value", target = "text")
fun convert(v: Int): Int = v // error: text is a String
}
Fix:
- Change the function's return type to the target property's type.
- Or point
targetat a property of the returned type.
KRAFT-E037 — @MapUsing extension with parameters¶
Severity: error
Cause: A @MapUsing extension function declares value parameters. Only the receiver is used as input.
@MapUsing(target = "text")
fun Src.format(prefix: String): String = prefix + value // error: extension takes no parameters
Fix:
- Remove the value parameters from the extension function; read what you need from the receiver.
KRAFT-E038 — Invalid @MapUsing signature¶
Severity: error
Cause: A @MapUsing function is neither an extension function nor a function with exactly one parameter.
@MapUsing(target = "text")
fun convert(a: Int, b: Int): String = "" // error: two parameters, not an extension
Fix:
- Give the function exactly one parameter, or make it an extension function.
KRAFT-E039 — @MapUsing without a return type¶
Severity: error
Cause: A @MapUsing function has no return type that Kraft can read. Kotlin infers return types even when they are not written, so this is a defensive check.
Fix:
- Declare an explicit return type on the function.
KRAFT-E040 — @MapUsing type is not a class¶
Severity: error
Cause: The parameter or return type of a @MapUsing function is not a class, for example a type parameter.
@MapUsing(target = "text")
fun <T> convert(v: T): String = "" // error: parameter type is a type parameter
Fix:
- Use concrete class types for the parameter and the return type.
KRAFT-E041 — @MapUsing parameter type mismatch¶
Severity: error
Cause: The parameter type of a property-source @MapUsing function does not match the type of the source property it reads.
data class Src(val value: Int)
@MapConfig(source = Src::class, target = Dst::class)
object SrcMapper {
@MapUsing(source = "value", target = "text")
fun convert(v: String): String = v // error: value is an Int
}
Fix:
- Change the function's parameter type to the source property's type.
- Or point
sourceat a property of the parameter's type.
KRAFT-E042 — @MapIgnoreField with a blank name¶
Severity: error
Cause: @MapIgnoreField has a blank name.
@MapConfig(
source = User::class,
target = UserDto::class,
ignoredMappings = [MapIgnoreField(name = "")], // error: blank name
)
object UserMapper
Fix:
- Set
nameto the property to ignore.
KRAFT-E043 — @MapIgnoreField unknown direction¶
Severity: error
Cause: @MapIgnoreField has a direction that names an enum entry the processor does not know. Valid source cannot normally produce this, so it points at a processor and annotations version mismatch.
Fix:
- Use one of the
IgnoreSidevalues:TARGET,SOURCEorBOTH. - Make sure the Kraft annotations artifact and the Kraft KSP processor are the same version.
KRAFT-E044 — @MapEnum type is not an enum¶
Severity: error
Cause: A @MapEnum source or target (or a from/to type given to the enum generator) is not an enum class.
data class NotAnEnum(val value: Int)
enum class Status { ACTIVE }
@MapEnum(source = NotAnEnum::class, target = Status::class) // error: source is not an enum
object StatusMapper
Fix:
- Point
@MapEnumsourceandtargetat enum classes.
KRAFT-E045 — @MapEnum duplicate source entry¶
Severity: error
Cause: A source enum entry appears more than once in the fieldMappings of a @MapEnum.
@MapEnum(
source = Status::class,
target = StatusDto::class,
fieldMappings = [
FieldMapping(source = "ACTIVE", target = "ON"),
FieldMapping(source = "ACTIVE", target = "OFF"), // error: ACTIVE mapped twice
],
)
object StatusMapper
Fix:
- Keep one
FieldMappingper source enum entry.
KRAFT-E046 — @MapEnum malformed field mapping¶
Severity: error
Cause: A FieldMapping inside @MapEnum has a source or target that is missing or not a String.
@MapEnum(
source = Status::class,
target = StatusDto::class,
fieldMappings = [FieldMapping(source = 1, target = "ON")], // error: source must be a String
)
object StatusMapper
Fix:
- Give both
sourceandtargetas String literals naming enum entries.
KRAFT-E047 — @MapEnum unknown source entry¶
Severity: error
Cause: A FieldMapping in @MapEnum names a source entry that is not a value of the source enum.
@MapEnum(
source = Status::class,
target = StatusDto::class,
fieldMappings = [FieldMapping(source = "MISSING", target = "ON")], // error: Status has no MISSING
)
object StatusMapper
Fix:
- Correct the
sourcename to an entry of the source enum.
KRAFT-E048 — @MapEnum unknown target entry¶
Severity: error
Cause: A FieldMapping in @MapEnum names a target entry that is not a value of the target enum.
@MapEnum(
source = Status::class,
target = StatusDto::class,
fieldMappings = [FieldMapping(source = "ACTIVE", target = "MISSING")], // error: StatusDto has no MISSING
)
object StatusMapper
Fix:
- Correct the
targetname to an entry of the target enum.
KRAFT-E049 — @KraftConverter with generic types¶
Severity: error
Cause: A @KraftConverter has a parameterized receiver or return type. Converters are registered by their raw source and target class pair, so type arguments cannot be matched.
@KraftConverter
fun List<Int>.toLabel(): String = joinToString() // error: List<Int> has a type argument
Fix:
- Rely on element-wise conversion:
List/Setproperties whose element types map are already converted without a@KraftConverter. - For any other parameterized conversion, use a per-property
@MapUsingon the mapper.
KRAFT-E050 — Ambiguous @KraftConverter¶
Severity: error
Cause: Two @KraftConverter functions in the same module register the same source and target pair.
@KraftConverter fun Int.toLabel(): String = "a$this"
@KraftConverter fun Int.toLabelAgain(): String = "b$this" // error: Int -> String already registered
Fix:
- Remove one of the two converters.
- Or wrap the alternative call site with
@MapUsing.
KRAFT-E051 — @KraftConverter not top-level¶
Severity: error
Cause: A @KraftConverter function is declared inside a class or object instead of at the top level of a file.
Fix:
- Move the function to the top level of a file.
KRAFT-E052 — @KraftConverter not an extension¶
Severity: error
Cause: A @KraftConverter function has no receiver. The receiver type is the source type of the conversion.
Fix:
- Turn the function into an extension:
fun Int.toLabel(): String.
KRAFT-E053 — @KraftConverter with parameters¶
Severity: error
Cause: A @KraftConverter extension function declares value parameters. Only the receiver is used as input.
@KraftConverter
fun Int.toLabel(prefix: String): String = prefix + this // error: parameter not allowed
Fix:
- Remove the value parameters; read what you need from the receiver.
KRAFT-E054 — @KraftConverter without a return type¶
Severity: error
Cause: A @KraftConverter function has no return type that Kraft can read. Kotlin infers return types even when they are not written, so this is a defensive check.
Fix:
- Declare an explicit return type:
fun Int.toLabel(): String.
KRAFT-E055 — Annotation argument is not a String¶
Severity: error
Cause: An annotation argument that must be a String has a different type.
@MapConfig(
source = A::class,
target = B::class,
fieldMappings = [FieldMapping(source = 1, target = "a")], // error: source must be a String
)
object M
Fix:
- Pass a String literal for the argument.
KRAFT-E056 — Annotation argument is not an enum value¶
Severity: error
Cause: An annotation argument that must be an enum value has a different type.
@MapUsing(target = "text", direction = 1) // error: direction must be a ConverterDirection
fun convert(v: Int): String = v.toString()
Fix:
- Pass an enum constant, for example
ConverterDirection.FORWARD.
KRAFT-E057 — Annotation argument is not an array¶
Severity: error
Cause: An annotation argument that must be an array or list (such as fieldMappings) has a different type.
@MapConfig(source = A::class, target = B::class, fieldMappings = FieldMapping("a", "b")) // error: not an array
Fix:
- Pass an array literal:
fieldMappings = [FieldMapping("a", "b")].
KRAFT-E058 — Invalid side configuration¶
Severity: error
Cause: The kraft { } side options (kraft.side.* KSP arguments) are invalid, for example two side patterns that overlap.
kraft {
side("outer") { packagePattern = "**.data.**" }
side("inner") { packagePattern = "**.data.api.**" } // error: pattern is a subset of 'outer'
}
Fix:
- Fix the side configuration in
build.gradle.kts; the message states which rule it broke. - Make the package patterns of different sides disjoint.
KRAFT-E059 — Delegate name collision¶
Severity: error
Cause: Two distinct converter type pairs hash to the same generated delegate name. This is not a mistake in your code.
Fix:
- Report it on the Kraft issue tracker with the type names from the message.
KRAFT-E060 — Ambiguous @MapEnum¶
Severity: error
Cause: More than one @MapEnum declaration registers the same source and target pair.
@MapEnum(source = Status::class, target = StatusDto::class)
object FirstEnumMapper
@MapEnum(source = Status::class, target = StatusDto::class) // error: pair already registered
object SecondEnumMapper
Fix:
- Remove one of the
@MapEnumdeclarations. - Or change one of the source/target pairs.
KRAFT-E061 — Enum converter conflict¶
Severity: error
Cause: A hand-written @KraftConverter and a @MapEnum-generated mapper register the same enum pair.
@MapEnum(source = Status::class, target = StatusDto::class)
object StatusMapper
@KraftConverter
fun Status.toDto(): StatusDto = StatusDto.valueOf(name) // error: Status -> StatusDto is also @MapEnum-generated
Fix:
- Remove one of the two; the
@MapEnum-generated mapper already auto-resolves for this enum pair.
KRAFT-E062 — Alias collision¶
Severity: error
Cause: Two mappers generate the same side alias name for the same source class, so one would shadow the other.
// package app.shared is configured as side "Shared"
@MapConfig(source = Source::class, target = TargetA::class)
object SourceToTargetAMapper
@MapConfig(source = Source::class, target = TargetB::class) // error: both generate Source.toShared()
object SourceToTargetBMapper
Fix:
- Disambiguate via the side template, for example
to{side}{target}instead ofto{side}. - Or set
aliasEmitMode = AliasEmitMode.FULL_NAME_ONLYon one of the mappers.
KRAFT-E063 — Lossy collection conversion¶
Severity: error
Cause: A collection property would be converted into a Set or MutableSet from something that is not a Set or MutableSet. Duplicate elements would be dropped without a trace, so Kraft refuses to generate the conversion. It applies at every nesting level and to the reverse mapper that @MapReverse generates: a mapper from Set to List forward is fine, but its reverse would turn a List into a Set.
The deprecated @MapNested and nestedMappings paths are frozen at their 0.13 behaviour and are not checked.
data class ItemSource(val id: Int)
data class ItemDto(val id: Int)
data class Source(val items: List<ItemSource>)
data class Target(val items: Set<ItemDto>) // error: converting List<ItemSource> to Set<ItemDto> would drop duplicate elements
@MapConfig(source = Source::class, target = Target::class)
object SourceToTargetMapper
Fix:
- Change the target to
List<ItemDto>(or another collection that keeps duplicates). - Or map the property with
@MapUsingand call.toSet()explicitly, which makes the dropped duplicates your decision.
KRAFT-E064 — Map key mapping can merge entries¶
Severity: error
Cause: The key types of a Map or MutableMap property differ, and the key operation is one Kraft cannot prove one-to-one. If two different source keys became the same target key, all but one of their entries would be dropped without a trace. Kraft maps keys only through a value-class wrap or unwrap, or an enum mapping that maps no two source entries to the same target entry (auto-derived enum mappings always qualify). It refuses a nested mapper, a @KraftConverter, a @MapEnum that maps two or more source entries to one target entry, and an enum mapper or converter compiled in another module, whose entry table it cannot see. The message names the operation.
enum class StatusDto { OPEN, REOPENED, CLOSED }
enum class Status { ACTIVE, CLOSED }
@MapEnum(
source = StatusDto::class,
target = Status::class,
fieldMappings = [
FieldMapping(source = "OPEN", target = "ACTIVE"),
FieldMapping(source = "REOPENED", target = "ACTIVE"),
]
)
object StatusMapping
data class Source(val counts: Map<StatusDto, Int>)
data class Target(val counts: Map<Status, Int>) // error: @MapEnum StatusDto → Status maps OPEN, REOPENED to ACTIVE and would silently drop entries
@MapConfig(source = Source::class, target = Target::class)
object SourceToTargetMapper
The same @MapEnum is fine for map values (Map<String, StatusDto> into Map<String, Status>): merging values loses nothing.
Fix:
- Keep the key type the same on both sides.
- Or use a value-class key, or an enum key whose mapping is one-to-one.
- Or map the property with
@MapUsing, which makes the merge your decision.
KRAFT-E065 — Mapper dropped without a diagnostic¶
Severity: error
Cause: Kraft found no way to map a target property and no rule reported an error for it, so the mapper would have vanished without a word. This is an internal guard: with the built-in rules every unmappable property already gets a more specific error, so only a custom rule chain or a Kraft bug can reach it.
Fix:
- Report it as a Kraft bug, with the source and target types of the mapper.
KRAFT-E066 — Rule declared in both annotation forms¶
Severity: error
Cause: One @MapConfig object gives a @FieldMapping for the same target, or a @MapIgnoreField for the same name, both in the @MapConfig array (fieldMappings / ignoredMappings) and as an annotation on the object. Kraft refuses it even when the two rules are identical, because it cannot tell which one you meant. For @MapIgnoreField the direction does not matter: the same name in both forms is a conflict. Two rules for one target inside the same form are not reported: the last one wins, as before.
@MapConfig(
source = UserDto::class,
target = User::class,
fieldMappings = [FieldMapping(source = "name", target = "fullName")]
)
@FieldMapping(source = "name", target = "fullName") // error: 'fullName' is declared in both forms
object UserMapper
The error reads:
[KRAFT-E066] @FieldMapping for target 'fullName' is declared both in @MapConfig.fieldMappings and as an annotation on 'UserMapper'. Keep one.
Fix:
- Delete one of the two declarations.
- Prefer one form per object: the annotations on the object, or the
@MapConfigarray.
KRAFT-E067 — @MapConstant target not found¶
Severity: error
Cause: A @MapConstant member fills a target that is not a constructor parameter of the class the mapper builds. The target is the member's name, or @MapConstant(target = ...) when that is set. On a @MapReverse mapper the check uses the class of the constant's direction: the @MapConfig target for a forward constant, the @MapConfig source for a reverse one.
data class User(val name: String)
@MapConfig(source = UserDto::class, target = User::class)
object UserMapper {
@MapConstant val curency = "HUF" // error: User has no parameter 'curency'
}
The error reads:
[KRAFT-E067] @MapConstant 'UserMapper.curency' targets 'curency', which is not a constructor parameter of 'User'.
Fix:
- Name the member after the parameter, or set
@MapConstant(target = "<parameter>"). - On a
@MapReversemapper, checkdirection:FORWARDfills the@MapConfigtarget,REVERSEfills the source.
KRAFT-E068 — @MapConstant type mismatch¶
Severity: error
Cause: The type of a @MapConstant member (a property's type or a function's return type, declared or inferred) cannot be assigned to its target parameter. A non-null member may fill a nullable target; a nullable member cannot fill a non-null one. A type Kraft cannot resolve is shown as <unresolved>.
data class User(val name: String, val isActive: Boolean)
@MapConfig(source = UserDto::class, target = User::class)
object UserMapper {
@MapConstant val isActive = "yes" // error: String is not Boolean
}
The error reads:
[KRAFT-E068] @MapConstant 'UserMapper.isActive' has type String, but target 'isActive' in 'User' has type Boolean.
Fix:
- Give the member the target's type, for example by declaring it:
@MapConstant val isActive: Boolean = true. - For a nullable member, make it non-null, or make the target nullable.
KRAFT-E069 — Invalid @MapConstant member¶
Severity: error
Cause: The generated mapper must be able to read a @MapConstant member as UserMapper.member or call it as UserMapper.member(). So the member must be declared directly in a @MapConfig object, must not be private, and, for a function, must take no parameters and must not be an extension. Each rejected member gets one error, with one of these first lines:
[KRAFT-E069] @MapConstant 'UserMapper.currency' must not be private: the generated mapper cannot read it.
[KRAFT-E069] @MapConstant function 'UserMapper.currency' must take no parameters. To compute a value from the source, use @MapUsing.
[KRAFT-E069] @MapConstant function 'UserMapper.currency' must not be an extension function. To compute a value from the source, use @MapUsing.
[KRAFT-E069] @MapConstant 'Defaults.currency' must be declared in a @MapConfig object.
@MapConfig(source = UserDto::class, target = User::class)
object UserMapper {
@MapConstant fun UserDto.currency() = "HUF" // error: extension function
}
Fix:
- Remove
private(internaland public members both work), the parameters, or the receiver. - To compute the value from the source object, use
@MapUsinginstead. - Move a member outside a
@MapConfigobject into the@MapConfigobject of its mapper.
KRAFT-E070 — More than one rule for one target¶
Severity: error
Cause: A target property filled by a @MapConstant is also claimed, in the same direction, by another @MapConstant, a @FieldMapping (either form), a @MapUsing converter or a @MapIgnoreField. Kraft does not pick one. On a @MapReverse mapper the directions are checked separately: in reverse, a @FieldMapping claims its source name, and a @MapIgnoreField claims its name when its direction is SOURCE or BOTH.
@MapConfig(source = UserDto::class, target = User::class)
@FieldMapping(source = "cur", target = "currency")
object UserMapper {
@MapConstant val currency = "HUF" // error: 'currency' is also renamed from 'cur'
}
The error reads:
[KRAFT-E070] Target 'currency' in 'User' has more than one rule on 'UserMapper': @MapConstant 'currency' and @FieldMapping(source = "cur", target = "currency"). Keep one.
The other rule is shown as @FieldMapping(source = "...", target = "..."), @MapUsing '<function>', @MapIgnoreField("<name>") or @MapConstant '<member>'.
Fix:
- Delete the rule you do not want for that target.
- On a
@MapReversemapper, set@MapConstant(direction = ...)when the two rules are meant for different directions.
KRAFT-E071 — Flat rule without @MapConfig¶
Severity: error
Cause: A @FieldMapping or @MapIgnoreField is written directly on a declaration that has no @MapConfig. Kraft reads these annotations only from a @MapConfig object, so on any other class they would be ignored without a word. A typical case is a @MapFrom / @MapTo class, where the per-property annotations are @MapField and @MapIgnore.
@MapFrom(UserDto::class)
@FieldMapping(source = "name", target = "fullName") // error: no @MapConfig on User
data class User(val fullName: String)
The error reads:
[KRAFT-E071] @FieldMapping on 'User' requires @MapConfig on the same object. Use @MapField on the property for @MapFrom/@MapTo classes.
For @MapIgnoreField the hint is Use @MapIgnore on the property for @MapFrom/@MapTo classes.
Fix:
- On a
@MapFrom/@MapToclass, remove the annotation and put@MapField(counterPartName = ...)(rename) or@MapIgnore(ignore) on the property. - Or move the rule to a
@MapConfigobject for the same source and target.
Warnings¶
KRAFT-W001 — Redundant @MapUsing¶
Severity: warning
Cause: A property-source @MapUsing (one with a source) claims a target property that Kraft would map on its own: a direct copy, a nested or element-wise collection mapping, a value-class wrap or unwrap, or a registered @KraftConverter. Kraft still calls your converter and still generates the mapper. The warning shows the assignment Kraft would have generated instead, so you can check that it is equivalent. Kraft cannot read function bodies, so it compares outcomes, not code. Whole-source @MapUsing (no source) is never reported. Neither is a @MapUsing that replaces a global @KraftConverter for the same property types: Kraft treats that as a deliberate per-mapper override. A global converter applied element-wise to a collection, or through ?. to a nullable value, is still reported.
data class RowDto(val id: Int)
data class Row(val id: Int)
data class ReportDto(val rows: List<RowDto>?)
data class Report(val rows: List<Row>)
@MapConfig(source = RowDto::class, target = Row::class)
object RowMapper
@MapConfig(source = ReportDto::class, target = Report::class)
object ReportMapper {
@MapUsing(source = "rows", target = "rows") // warning: Kraft already maps List<RowDto>? to List<Row>
fun List<RowDto>?.toRows(): List<Row> = this?.map { it.toRow() } ?: emptyList()
}
The warning reads:
[KRAFT-W001] @MapUsing 'toRows' on target property 'rows' duplicates a mapping Kraft already generates.
Kraft would generate:
rows = this.rows?.map { it.toRow() } ?: emptyList()
Remove 'toRows', or add @Suppress("KRAFT-W001") to it if its body intentionally differs (filtering, sorting, defaults).
Fix:
- Delete the converter. Kraft generates the assignment shown in the warning.
- If the converter deliberately does something different (it filters, sorts, or supplies a different default), keep it and suppress the warning on the function:
@Suppress("KRAFT-W001")
@MapUsing(source = "rows", target = "rows")
fun List<RowDto>?.toRows(): List<Row> = this.orEmpty().filter { it.id > 0 }.map { it.toRow() }
Suppressing the warning does not change the generated code.
KRAFT-W002 — Nullable-twin @KraftConverter¶
Severity: warning
Cause: A same-module @KraftConverter maps S? → T? while a S → T converter is also registered, in the same module or upstream. Kraft already reuses the S → T converter for nullable S? → T? properties through a safe call (this.<property>?.<fn>()), so the nullable converter only lifts it through null. Both converters stay registered and the generated code is unchanged. A nullable converter in an upstream module is never reported.
@KraftConverter
fun ReasonDto.toReason(): Reason = Reason(code)
@KraftConverter // warning: only lifts toReason through null
fun ReasonDto?.toReasonNullable(): Reason? = this?.toReason()
The warning reads:
[KRAFT-W002] @KraftConverter 'toReasonNullable' (ReasonDto? → Reason?) only lifts 'toReason' (ReasonDto → Reason) through null.
Kraft already generates 'this.<property>?.toReason()' for nullable ReasonDto? → Reason? properties. Remove 'toReasonNullable', or add @Suppress("KRAFT-W002") if it does more than a safe call.
When the non-null converter comes from an upstream module, the message names the generated delegate function instead of your original function.
Fix:
- Delete the nullable converter. Kraft generates the safe call for you.
- If it does more than a safe call (for example it supplies a different value for
null), keep it and suppress the warning on the function:
@Suppress("KRAFT-W002")
@KraftConverter
fun ReasonDto?.toReasonNullable(): Reason? = this?.toReason() ?: Reason.UNKNOWN
KRAFT-W003 — @MapIgnoreField(BOTH) property not found¶
Severity: warning
Cause: A BOTH-direction @MapIgnoreField names a property that is missing from the target of one direction. The ignore is skipped for that direction.
@MapConfig(
source = User::class,
target = UserDto::class,
ignoredMappings = [MapIgnoreField("legacyId")], // warning: UserDto has no 'legacyId'
)
object UserMapper
Fix:
- Remove the
@MapIgnoreField, or limit it to the direction where the property exists withdirection = IgnoreSide.SOURCEorTARGET.
KRAFT-W004 — @MapNested overrides @MapField¶
Severity: warning
Cause: @MapNested and @MapField are both on one property. @MapNested takes precedence and @MapField is ignored.
@MapFrom(User::class)
data class UserDto(
@MapField("home")
@MapNested // warning: @MapNested takes precedence
val address: AddressDto,
)
Fix:
- Remove
@MapField, and use@MapNested(sourceName = "home")for the rename.
KRAFT-W005 — Redundant @MapNested sourceName¶
Severity: warning
Cause: @MapNested(sourceName = ...) repeats the property's own name, so the argument has no effect.
@MapFrom(User::class)
data class UserDto(
@MapNested(sourceName = "address") // warning: same as the property name
val address: AddressDto,
)
Fix:
- Remove the
sourceNameargument.
KRAFT-W006 — Multiple mapper generator providers¶
Severity: warning
Cause: More than one MapperGeneratorProvider service is on the processor classpath. Kraft uses the first one it finds.
// Two artifacts on the KSP classpath each ship a META-INF/services entry
// for MapperGeneratorProvider. // warning
Fix:
- Remove the extra generator artifact from the
kspconfiguration so a single provider remains.
KRAFT-W007 — Multiple enum generator providers¶
Severity: warning
Cause: More than one EnumMapperGeneratorProvider service is on the processor classpath. Kraft uses the first one it finds.
// Two artifacts on the KSP classpath each ship a META-INF/services entry
// for EnumMapperGeneratorProvider. // warning
Fix:
- Remove the extra generator artifact from the
kspconfiguration so a single provider remains.
KRAFT-W008 — Mapper generation skipped¶
Severity: warning
Cause: Kraft skipped generating a mapper because neither the source nor the target class has an originating file, which KSP needs for incremental processing.
// Both types come from outside the module's source files (for example synthetic or
// generated classes with no containing file). // warning
Fix:
- Declare the source or target type in a source file of the module.
- Or write the mapping by hand.
KRAFT-W009 — Nullable property set to null¶
Severity: warning
Cause: A nullable target property has no mapping source (no same-named source property, no @MapField or FieldMapping rename, no @MapIgnore / @MapIgnoreField entry and no @MapUsing) and no default value. Kraft still generates the mapper and passes null for it, which may hide a forgotten mapping. A property with a default value is left out of the constructor call instead, with no warning.
data class PersonSource(val name: String)
data class PersonDto(val name: String, val nickname: String?) // warning: 'nickname' has no source
@MapConfig(source = PersonSource::class, target = PersonDto::class)
object PersonMapper
// generates PersonDto(name = this.name, nickname = null)
Fix:
- Give the property a default (
val nickname: String? = null) to make null the intended value. - Or map it explicitly with
@MapUsing(target = "nickname"). - Or set it from the mapper object:
@MapConstant val nickname = ...(see Constant Values). - Or add
@Suppress("KRAFT-W009")to the property or to the mapping declaration (the@MapConfigobject, or the@MapFrom/@MapToclass). Suppression does not change the generated code.