Validate once at the boundary
Keep uncertain external data separate from validated domain objects. Make required properties non-null after checking them, and preserve null only where absence is meaningful.
data class Profile(val id: String, val nickname: String?)
fun profile(id: String?, nickname: String?): Profile? {
val validId = id?.trim()?.takeIf { it.isNotEmpty() } ?: return null
val optionalName = nickname?.trim()?.takeIf { it.isNotEmpty() }
return Profile(validId, optionalName)
}
The factory returns null for invalid identity, while a missing nickname is allowed. A richer validation result is better if the UI needs to explain exactly what failed. The key is to make this choice explicit rather than letting null stand for network failure, invalid input, and legitimate absence simultaneously.
Avoid chains of defaults that manufacture a seemingly valid object from corrupt input. A fabricated empty ID can travel far beyond the original problem and make persistence bugs difficult to diagnose.
Exercise
Replace the nullable factory result with a sealed success/error type that identifies a missing ID. Keep nickname absence valid.
Check: an invalid ID must never produce a Profile; a valid ID and null nickname must succeed.