Skip to main content

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.

DiagnosticOPEN_API_WITHOUT_SUBCLASS_OPT_IN
Default severityError
Gradle propertyopenApiWithoutSubclassOptIn
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. */
@RequiresOptIn
public 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 internal types are not reported by either diagnostic because library users cannot subclass them in Kotlin source.
  • A @SubclassOptInRequired annotation with no marker classes gates nothing. It is reported by the separate SUBCLASS_OPT_IN_WITHOUT_MARKERS check instead of this one.
  • A class whose constructors are all internal or private can't be subclassed outside the library, so it is never reported, even if it is open or abstract.
  • fun interfaces are checked like any other interface.
  • Sealed interfaces are not reported here. They are covered by EXHAUSTIVE_PUBLIC_API instead.

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

See also