Open API without subclass opt-in
OPEN_API_WITHOUT_SUBCLASS_OPT_IN reports public open or abstract classes and interfaces that
can be subclassed outside the library without any restriction.
| Diagnostic | OPEN_API_WITHOUT_SUBCLASS_OPT_IN |
| Default severity | Error |
| Gradle property | openApiWithoutSubclassOptIn |
| Exemption | @IntentionallyOpen |
What it reports
A public open/abstract class with at least one public or protected constructor, or a public
non-sealed interface.
/** Base type for components hosted by an application. */public open class Component
Rationale
Once external code subclasses a type, the library can no longer freely add abstract members, change existing members' signatures, or tighten invariants without breaking those subclasses. Unrestricted open API is one of the classic ways a public declaration becomes hard to evolve.
Don't
/** Base type for UI elements rendered by an application. */public open class Widget/** Extension point invoked during application startup. */public interface Plugin {/** Initializes the plugin for the current application. */public fun run()}
Do
/** A UI widget whose internal constructor prevents external subclasses. */public open class Widget internal constructor()/** Marks APIs that require an opt-in. */@RequiresOptInpublic annotation class InternalMyLibrarySubclassApi/** A plugin implemented under an opt-in contract. */@SubclassOptInRequired(InternalMyLibrarySubclassApi::class)public interface Plugin {/** Initializes the plugin for the current application. */public fun run()}
@SubclassOptInRequired requires external subclasses to opt in to the explicitly unstable contract.
Notes
@PublishedApi internaltypes are not reported by either diagnostic because library users cannot subclass them in Kotlin source.- A
@SubclassOptInRequiredannotation with no marker classes gates nothing. It is reported by the separateSUBCLASS_OPT_IN_WITHOUT_MARKERScheck instead of this one. - A class whose constructors are all
internalorprivatecan't be subclassed outside the library, so it is never reported, even if it isopenorabstract. fun interfaces are checked like any other interface.- Sealed interfaces are not reported here. They are covered by
EXHAUSTIVE_PUBLIC_APIinstead.
Exemption
When unrestricted subclassing is an intended, stable part of the contract, acknowledge it instead of adding an opt-in marker:
/** A UI widget deliberately open to external subclasses. */@IntentionallyOpen(reason = ExemptionReason.API_DESIGN)public open class Widget
@IntentionallyOpen targets the class declaration only.
Configuration
apiWatchdog {openApiWithoutSubclassOptIn = WatchdogSeverity.WARNING}
With direct compiler invocation:
-P plugin:org.jetbrains.kotlin.library.api.watchdog:diagnosticSeverity=OPEN_API_WITHOUT_SUBCLASS_OPT_IN:warning