Skip to content

Kotlin Symbol Processor that generates type-safe builder DSLs from annotated interfaces

License

Notifications You must be signed in to change notification settings

izantech/auto-builder

Repository files navigation

AutoBuilder

AutoBuilder is a symbol processor that generates builder classes from definition interfaces:

@AutoBuilder
interface User {
    val name: String
    val age: Int
}

// Later in the code...

val user = User {
    name = "John"
    age = 30
}
val userCopy = user.copy { age = 40 }

This library is inspired by Kotlin data classes, but without the binary compatibility issues that those may cause. For more information about the motivation behind this library, see this article from Jake Wharton.

Setup

dependencies {
    implementation("io.github.izanrodrigo:autobuilder-annotations:${latest_version}")
    ksp("io.github.izanrodrigo:autobuilder-processor:${latest_version}")
}

Quick Start

@AutoBuilder

The @AutoBuilder annotation is used to define the interface that will be used to generate the builder class:

@AutoBuilder
interface User {
    val name: String
    val age: Int
}

The code above will generate the following files:

  • User.builder.kt
  • User.defaults.kt

See INTERNALS.md for more details.

In order to instantiate the User, we can use the appropriate method for Kotlin or Java:

Kotlin

val user = User {
    name = "John"
    age = 30
}

Java

var user = UserBuilder()
    .setName("John")
    .setAge(30)
    .build();

That object can be copied or mutated using the copy method:

Kotlin

val userCopy = user.copy { age = 40 }

Java

In Java the copy method is not available, but we can create a new builder reusing the previous user data:

var userCopy = UserBuilder(user).setAge(40).build();

@DefaultValue

By default, the generated builder will generate default values for some basic types. However, for more complex types or custom default values, we can use the @DefaultValue annotation:

import java.util.UUID

@AutoBuilder
interface User {
    // Other properties...

    val id: UUID
        @DefaultValue get() = UUID.randomUUID()
}

NOTE: If you forget to add default values to non-null properties, there will be a compile error:

auto-builder-compilation-error

@Lateinit

However, there are instances where the property is not meant to be nullable nor have a default value. In those cases, we can use the @Lateinit annotation:

data class Credentials(val username: String, val password: String)

@AutoBuilder
interface User {
    // Other properties...

    @Lateinit val credentials: Credentials
}

In this case, the credentials property will be required when building the User object.

NOTE: If you forget to set a value to a @Lateinit property, there will be a runtime error:

auto-builder-runtime-error

@UseBuilderSetter

This annotation is used to force the use of the builder setter instead of the property itself.

@AutoBuilder
interface Interaction {
    // Other properties...

    @UseBuilderSetter
    val onClick: () -> Unit
        @DefaultValue get() = {}
}

// Later in the code...

val interaction = Interaction {
    onClick = {} // This will throw an error!!!

    setOnClick { println("Clicked!") } // This will work.
}

Inferred default values

When the default value can be inferred from the property type, the @DefaultValue annotation is not needed. The default values for the following types are inferred:

  • String, CharSequence""
  • Byte, Int, Number, Short, ➞ 0
  • Long0L
  • Float0.0f
  • Double0.0
  • Char'' // NULL char
  • Booleanfalse
  • BigDecimalBigDecimal.ZERO
  • BigIntegerBigInteger.ZERO
  • Array<T>emptyArray<T>()
  • List<T>emptyList<T>()
  • Set<T>emptySet<T>()
  • Map<K, V>emptyMap<K, V>()
  • IntArray -> intArrayOf()
  • LongArray -> longArrayOf()
  • FloatArray -> floatArrayOf()
  • DoubleArray -> doubleArrayOf()
  • BooleanArray -> booleanArrayOf()
  • ByteArray -> byteArrayOf()
  • ShortArray -> shortArrayOf()
  • CharArray -> charArrayOf()
  • AnnotatedStringAnnotatedString("")

More types will be added in the future.

License

Copyright 2025 Izan Rodrigo Moreno Maroto.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

About

Kotlin Symbol Processor that generates type-safe builder DSLs from annotated interfaces

Topics

Resources

License

Stars

Watchers

Forks

Sponsor this project

  •  

Languages