Skip to main content

Exhaustive public API

EXHAUSTIVE_PUBLIC_API reports public enums and sealed hierarchies, which users can match exhaustively with a when expression that has no else branch.

DiagnosticEXHAUSTIVE_PUBLIC_API
Default severityError
Gradle propertyexhaustivePublicApi
Exemption@IntentionallyExhaustive

What it reports

The check reports every public or protected enum class, sealed class, and sealed interface declaration.

/** Whether the service can accept requests. */
public enum class Status {
/** The service is ready to accept requests. */
ACTIVE,
/** The service rejects requests until it is reactivated. */
INACTIVE,
}

Rationale

A user can write a when over an enum or a sealed hierarchy without an else branch and have the compiler check exhaustiveness for them. That is convenient, but it also means the user code depends on today's exact set of entries or subtypes. Adding a new enum entry or a new subtype later makes every such when at every call site stop compiling: a source-incompatible change the library author did not think of as breaking. See the Kotlin API guidelines on preventing unwanted extensions.

Don't

/** Severity assigned to a log record. */
public enum class LogLevel {
/** Fine-grained information used to diagnose behavior. */
DEBUG,
/** Routine progress and state changes. */
INFO,
/** A failure that prevented an operation from completing. */
ERROR,
}
/** A change in the service lifecycle. */
public sealed interface Event {
/** Emitted after the service becomes ready. */
public class Started : Event
/** Emitted after the service finishes shutting down. */
public class Stopped : Event
}

Do

/** A logging level that can grow without breaking exhaustive matches. */
public class LogLevel {
/** Named logging levels. */
public companion object {
/** Fine-grained information used to diagnose behavior. */
@JvmField
public val DEBUG: LogLevel = LogLevel()
/** Routine progress and state changes. */
@JvmField
public val INFO: LogLevel = LogLevel()
/** A failure that prevented an operation from completing. */
@JvmField
public val ERROR: LogLevel = LogLevel()
}
}
/** A lifecycle event implemented under an opt-in contract. */
@SubclassOptInRequired(InternalMyLibrarySubclassApi::class)
public interface Event {
/** Emitted after the service becomes ready. */
public class Started : Event
/** Emitted after the service finishes shutting down. */
public class Stopped : Event
}

Notes

  • @PublishedApi internal types are not reported because library users cannot name and match them in source.

A non-final member of a sealed hierarchy (an abstract or sealed subclass) is itself unrestricted, subclassable API and is reported separately by Open API without subclass opt-in, on top of this check.

Exemption

Apply @IntentionallyExhaustive to the enum or sealed class or interface when a fixed set of entries or subtypes is a deliberate, stable part of the contract:

/** A fixed set of logging levels. */
@IntentionallyExhaustive(reason = ExemptionReason.API_DESIGN)
public enum class LogLevel {
/** Fine-grained information used to diagnose behavior. */
DEBUG,
/** Routine progress and state changes. */
INFO,
/** A failure that prevented an operation from completing. */
ERROR,
}

Configuration

apiWatchdog {
exhaustivePublicApi = WatchdogSeverity.WARNING
}

With direct compiler invocation:

-P plugin:org.jetbrains.kotlin.library.api.watchdog:diagnosticSeverity=EXHAUSTIVE_PUBLIC_API:warning

See also