Constructing constrained/validated objects
Remark on you DDD

I've never thought this is possible:

val email = Email("invalid") ?: throw IlllegalArgumentException


Kotlin was created with the intention of null-safety / explicit nullability, and your factory violates it. It allowes classes to be instanceated directly as NULL - this is really weird and for people admiring and working with Kotlin it's very confusing.

Improve current solution

I don't see an improvement for your current solution. I can only recommend to make a Factory class which would genereate all the boilterplate code for all of your generates classes, including converters. But I have a suggestion...

Suggestion

I would go away from extending a companion object and define 'Nullable' Builders, which inherit from generic one:

abstract class NullableValue&lt;T, D&gt;(
    private val init: () -&gt; T,
    private val value: D,
    private val check: (D) -&gt; Boolean
) {
    fun isValid(): Boolean = check(value)

    fun getOrNull(): T? = if (isValid()) init() else null
}


Define class and the builder:

class Email(val email: String)

class NullableEmail(val email: String) : NullableValue&lt;Email, String&gt;(
    init = { Email(email) },
    value = email,
    check = { "^.+@.+\\..+".toRegex().matches(it) }
)

class NonBlankString(val string: String)

class NullableNonBlankString(val string: String) : NullableValue&lt;NonBlankString, String&gt;(
    init = { NonBlankString(string) },
    value = string,
    check = String::isNotBlank
)


And you can use them:

    NullableEmail("").getOrNull() // will be null
    NullableNonBlankString("abc").getOrNull() // will be NonBlankString.class

    Converter&lt;NonBlankString, String&gt; { value -&gt;
        value.toString()
    }




If you want more reusability, we can use strategy pattern:

interface Nullable&lt;T&gt; {

    fun isValid(): Boolean

    fun getOrNull() : T?
}

abstract class NullableValue&lt;T, D&gt; (
    private val init: () -&gt; T,
    private val value: D,
    private val check: (D) -&gt; Boolean
) : Nullable&lt;T&gt;  {
    override fun isValid(): Boolean = check(value)

    override fun getOrNull(): T? = if (isValid()) init() else null
}

class NullableEmail(val email: String) : Nullable&lt;Email&gt; by NullableByRegex&lt;Email&gt; (
    init = { Email(email) },
    value = email
)