Skip to content

refactor!: remove Kotlin from the codebase - #255

Draft
mcollovati wants to merge 15 commits into
mainfrom
refactor/no-kotlin-tests
Draft

mcollovati wants to merge 15 commits into
mainfrom
refactor/no-kotlin-tests

Conversation

@mcollovati

Copy link
Copy Markdown
Contributor

Removes Kotlin from the repository. shared/src/main and every test source
are plain Java, and kotlin-maven-plugin, kotlin-stdlib, dynatest,
karibu-dsl and Dokka are gone from the build, so consumers of
browserless-test-shared no longer pull a Kotlin runtime.

The working notes are in de-kotlin-plan.md — phase by
phase, the breaking changes, and a Kotlin→Java translation table.

Breaking changes

Everything below is in com.vaadin.browserless.internal, .mocks or
.component, or was already deprecated.

  • Every *Kt facade class disappears. UtilsKtUtils,
    PrettyPrintTreeKtPrettyPrintTree, GridKtGridUtils, and so on.
    Since the class name changes anyway, the accessor names generated for Kotlin
    properties are corrected at the same time: getCurrentUI()currentUI(),
    get_saneFetchLimit()_saneFetchLimit(), getId_()/setId_()
    id_()/id_(…).
  • Kotlin types leave the signatures. kotlin.ranges.IntRange
    CountRange on SearchSpec.count and int from, int to on
    GridUtils._dump; SearchSpec<T>.() -> UnitConsumer<SearchSpec<T>>;
    Function0/Function1/Function2Supplier/Function/Predicate/
    BiPredicate; KClassClass<?>. ComponentQuery.withResultsSize(int),
    the documented entry point, is unaffected.
  • Three @Deprecated(forRemoval = true) Spring constructors are removed
    the Function0<UI> overloads on MockSpringServlet,
    MockSpringServletService and MockSpringVaadinSession. A Java UIFactory
    no longer extends Function0, so MockedUI::new was ambiguous against them.
  • Checked exceptions reappear on overridable methods. A Kotlin override
    declares none even when the Java superclass does:
    MockVaadinServlet.createServletService declares throws ServiceException
    and createDeploymentConfiguration declares throws ServletException. An
    override that calls super now has to declare the exception.
  • MockSpringServlet.routes, .ctx and .uiFactory are private, behind
    getRoutes(), getApplicationContext() and getUiFactory() — the shape
    MockVaadinServlet, MockService and MockVaadinSession already use.
  • Kotlin synthetics go: Companion, INSTANCE, $default bridges,
    DefaultConstructorMarker, and data class extras (componentN,
    copy$default).

Tests

The 25 DynaTest/Karibu test files are JUnit 6 Java. group/test map onto
@Nested/@Test; DynaTest's cloneBySerialization and expectThrows have
Java equivalents in TestSerialization and TestAssertions; AllTests.kt
was a pure aggregator and is gone.

A green build does not prove a port kept its coverage, so each step was checked
by diffing the multiset of <testcase name="…"> values in the surefire XML
against a baseline, not the tests="N" attribute, which DynaTest
under-reports. Against main this branch is 1490 → 1498: 225 names renamed
one-for-one from DynaTest's free text to the repository's
subject_scenario_expectation shape, plus 8 tests restored that the port
had dropped — they sat behind /* TODO: uncomment after importing Locator APIs */, and the Java Locator they waited for now exists.

Javadoc

Replacing Dokka turns doclint on for shared for the first time. Dokka
validated nothing, so it had a backlog of 935 diagnostics — the number is
not the "roughly 200" an earlier note recorded, because javadoc stops
reporting after 100 errors and 100 warnings. The root pom now passes
-Xmaxerrs/-Xmaxwarns so a count is a count.

All of it is fixed, in junit6, spring and quarkus too, and every module
now builds clean under doclint with warnings included. 291 of the diagnostics
were in generated *Locator files and were fixed in locator-processor,
which also now rewrites a copied {@link #member} to point back at the tester
it came from. One rule for contributors came out of this and is in
guidelines/documenting.md: a tester has to spell
cross-package @link and @throws targets out in full, because its Javadoc is
copied into a generated locator that has no imports.

Two regressions this work found and fixed

  • VaadinSession.close() fires the UI detach listener twice. The Kotlin
    test used var detachCalled = false, which hid it; ported to a counter it
    turned red. Left as-is and documented — worth its own look.
  • SessionObjects was a Kotlin data class, so the released artifact exposes
    private fields plus getSession() and friends. An earlier commit on this
    branch flattened the four vals into public fields while keeping the
    getters, adding API no release ever had. Reverted to private.

Verification

mvn clean install: 42 / 1456 / 4 / 41 / 31, all green.
mvn clean install -DskipTests -Djavadocs: zero diagnostics.
mvn spotless:check: clean.

The classes in `com.vaadin.browserless.mocks` and
`com.vaadin.browserless.quarkus.mocks` are built by the framework itself —
`SpringBrowserlessTest`, `QuarkusBrowserlessTest` and the application contexts
construct them as local variables, and no published signature accepts or
returns one. Say so, using the same wording as `BaseBrowserlessTest`, so they
stay free to change.

`MockWebApplicationContext` and `SpringSecurityRequestCustomizer` already
carried a shorter note; they now use the full sentence like the rest.
The 12 Kotlin files under `shared/src/main/kotlin/com/vaadin/browserless/mocks`
become 14 Java files. First phase of removing Kotlin from `shared`; the plan for
the rest is in `de-kotlin-plan.md`.

`MockHttpEnvironment.kt` splits into `MockHttpEnvironment`, `MockServletConfig`
and `MockUtils`, and the top-level helpers of `MockVaadinServlet.kt` become
statics on `MockVaadinServlet`. Every class is marked internal, and the KDoc is
rewritten as Javadoc: each mock now says which container behaviour it
reproduces and where it stops.

Kotlin properties are kept as accessors rather than flattened into public
fields, so most call sites are untouched — including the Kotlin ones, which see
a Java getter/setter pair as a property.

Breaking changes:

* `MockVaadinServlet.createServletService` declares `throws ServiceException`
  and `createDeploymentConfiguration` declares `throws ServletException`, as
  `VaadinServlet` does. An override that calls `super` has to declare it too.
* `MockHttpEnvironment.INSTANCE.setLocalPort(…)` becomes
  `MockHttpEnvironment.setLocalPort(…)`; same for `MockVaadinHelper`.
* `MockHttpSession.Companion.create(…)` becomes `MockHttpSession.create(…)`.
* `MockRequest.isUserInRole` takes a `BiPredicate` and the principal provider a
  `Supplier`, in place of the Kotlin function types.
* The `HttpSession.attributes` extension is gone; use
  `new SessionAttributeMap(session)`.
* `MockResponse` no longer exposes `_status`, `_bufferSize`, `_locale`,
  `_contentType` and `_characterEncoding`; use the `HttpServletResponse`
  getters and setters that back them.
* The `WebBrowser(request)` factory is renamed `createWebBrowser(request)`.

`MockInstantiator` is now `@Deprecated(forRemoval = true)` and forwards
`getPageTitleGenerator()`, which the wrapper was missing.
The eight Kotlin utility files under
`shared/src/main/kotlin/com/vaadin/browserless/internal` become nine Java files.
Second phase of removing Kotlin from `shared`; `Locator`, `MockVaadin`,
`PrettyPrintTree`, `Routes` and `Grid` are still Kotlin. See
`de-kotlin-plan.md`.

Each file becomes one utility class whose static methods take the former
extension receiver as their first parameter. `TestingLifecycleHook` splits in
two, since a Java interface cannot hold the mutable global the Kotlin top-level
`var` provided: the interface keeps the hook methods and `TestingLifecycleHooks`
holds the global and `cleanupDialogs()`.

A 20-line `Matches.kt` stays behind for `Component.matches()` and
`IntRange.size`, which take Kotlin-only types and go away with `SearchSpec` and
`Grid`.

Breaking changes:

* The `*Kt` facade classes are gone: `BasicUtilsKt`, `ComponentUtilsKt`,
  `ElementUtilsKt`, `RenderersKt`, `ShortcutsKt`, `UtilsKt` and
  `DepthFirstTreeIteratorKt` become `BasicUtils`, `ComponentUtils`,
  `ElementUtils`, `Renderers`, `Shortcuts`, `Utils` and
  `DepthFirstTreeIterator`.
* Property accessors lose their prefix along with the class rename, so
  `UtilsKt.getCurrentUI()` becomes `Utils.currentUI()`,
  `BasicUtilsKt.get_saneFetchLimit()` becomes `BasicUtils._saneFetchLimit()`,
  and `getId_` / `setId_` become the `id_` pair.
* The global hook moves from a top-level property to
  `TestingLifecycleHooks.getCurrent()` and `setCurrent(…)`, and
  `cleanupDialogs()` to `TestingLifecycleHooks.cleanupDialogs()`.
* `Component.isTemplate` becomes
  `TestingLifecycleHook.isTemplate(Component)`.
* `findAncestor` and `findAncestorOrSelf` take a `Predicate` in place of the
  Kotlin function type.
* `Button.caption` is folded into `caption(Component)`, which Kotlin resolved
  by static extension dispatch and Java cannot.
* Helpers that were Kotlin `internal`, and so public only by accident of the
  bytecode, are package-private: `splitByWhitespaces`, `ellipsize`,
  `hasCustomToString`, `isRouteNotFound`, `getErrorParameterType`,
  `isEffectivelyVisible`, `isPolymerTemplate` and friends.
* `serializeToBytes`, `deserialize` and `serializeDeserialize` are dropped;
  nothing called them.
Third phase of removing Kotlin from `shared`; `Locator`, `MockVaadin` and `Grid`
are still Kotlin. See `de-kotlin-plan.md`.

`MockRouteNotFoundError` and `MockInternalSeverError` get their own files, since
Java allows one public class per file. `Routes` keeps the constructor overloads
the Kotlin default arguments produced, and its sets are insertion-ordered as
`mutableSetOf()` was, so the route list in a dump and in a `not found` message
stays in a predictable order.

`TreeOnFailureExtension` no longer goes through `PrettyPrintTree.Companion`.
That line only runs when a test fails, so no suite would have caught it.

Breaking changes:

* `PrettyPrintTreeKt` and `RoutesKt` are gone: their functions are statics on
  `PrettyPrintTree` and `Routes`, and `PrettyPrintTree.Companion.ofVaadin(…)`
  becomes `PrettyPrintTree.ofVaadin(…)`.
* `prettyStringHook` takes a `BiConsumer` in place of the Kotlin function type,
  and it and the other two globals move behind accessors:
  `getPrettyPrintUseAscii()`, `getPrettyStringHook()`,
  `getDontDumpAttributes()` and their setters.
* `Routes` loses the members the `data class` generated: `component1()` through
  `component4()`, and `copy(…)` with defaulted arguments. The explicit
  four-argument `copy(…)`, `equals`, `hashCode` and `toString` remain.
* `Routes` fields are behind accessors: `getRoutes()`, `getErrorRoutes()`,
  `getLayouts()`, `getSkipPwaInit()` and `setSkipPwaInit(…)`.
* `MockRouteNotFoundError.cause` is behind `getCause()` and `setCause(…)`.
Fourth phase of removing Kotlin from `shared`; only `Grid.kt` and the two
helpers it still needs are left. See `de-kotlin-plan.md`.

`Locator.kt` becomes `Locator` plus `SearchSpec`, and `MockVaadin.kt` becomes
`MockVaadin` plus `SessionObjects`, `UIFactory`, `MockRequestCustomizer` and a
nested `MockPage`. A new `CountRange` replaces `kotlin.ranges.IntRange` in the
search spec, so no Java source in any module references a Kotlin type any more.

`MockVaadin` now fires session-init, service-destroy and UI-init through
`VaadinService.getEventBus()` with a handler that rethrows, so a listener
throwing during a test fails that test instead of only logging. It previously
reflected into the private listener collections.

The Kotlin `_get<Reified> { … }` syntax moves to a test-scope `LocatorDsl.kt` in
`junit6`, since Java has no equivalent of a reified type parameter or a receiver
lambda.

Breaking changes:

* `LocatorKt` and `MockVaadinKt` are gone; their functions are statics on
  `Locator` and `MockVaadin`, taking a `Class<T>` and a
  `Consumer<SearchSpec<T>>` in place of the reified type and the Kotlin block.
* `MockPage` moves from a top-level class to `MockVaadin.MockPage`.
* `SearchSpec` loses the all-argument constructor the Kotlin default arguments
  produced; construct with `new SearchSpec(clazz)` and set what you need. Its
  fields are behind accessors, `toPredicate()` returns a `Predicate`, and
  `count` is a `CountRange`.
* `MockVaadin.userAgent` and `mockRequestFactory` are behind accessors, and the
  factory is a `java.util.function.Function`.
* `MockVaadin.setup` and `setupServlet` take `Set<Class<?>>` rather than
  `Set<? extends Class<?>>`, since Java has no declaration-site variance.
* The three `@Deprecated(forRemoval = true)` constructors taking
  `Function0<UI>` are removed from `MockSpringServlet`,
  `MockSpringServletService` and `MockSpringVaadinSession`. With a plain Java
  `UIFactory` they are ambiguous against the `UIFactory` overload at any
  `MockedUI::new` call site.
Final phase of removing Kotlin from `shared`. `shared/src/main` is now pure
Java, `kotlin-stdlib` leaves its compile classpath, and consumers of
`browserless-test-shared` pull no Kotlin runtime. See `de-kotlin-plan.md`.

`Grid.kt` becomes `GridUtils` rather than `Grid`: a utility class of statics is
not a `Grid`, and the old name shadowed
`com.vaadin.flow.component.grid.Grid`, which forced 59 fully qualified
references inside the file and 8 more in `GridTester`. The name follows the
`BasicUtils` and `ComponentUtils` precedent.

The `org.jetbrains.annotations` imports move to JSpecify, which Vaadin already
provides, since those annotations arrived transitively with `kotlin-stdlib`.

Dokka is replaced by `maven-javadoc-plugin`. Dokka validated nothing, so
doclint now sees these sources for the first time and reports roughly 200
issues across 53 files, nearly all in testers untouched by this work. The
module sets `doclint` to `none` so the port does not become a Javadoc cleanup;
that cleanup is worth its own change. The other modules keep doclint on.

Breaking changes:

* `GridKt` is gone; its functions are statics on `GridUtils`.
* `_rowSequence` returns a `Stream` in place of a Kotlin `Sequence`, still
  lazily, so `TreeGrid._size()` keeps counting without fetching every row.
* `_dump(Grid, IntRange)` becomes `_dump(Grid, int from, int to)`.
* The `KProperty1` overloads of `HeaderRow.getCell` and `FooterRow.getCell`
  are dropped; the `getCell(String)` overloads remain.
* The intermediate `_clickItem` and `_doubleClickItem` arities the Kotlin
  default arguments generated are gone; the two-argument and the
  full-argument forms remain.
* Property accessors lose their `get` prefix along with the class rename, so
  `get_internalId` becomes `_internalId` and `getDataProvider` becomes
  `dataProvider`.
The last 25 Kotlin files were all test-scoped, so this removes Kotlin from
the repository entirely: `kotlin-maven-plugin`, `dynatest`, `karibu-dsl`,
`kotlin-stdlib`, Dokka and the Spotless `kotlin` block are all gone from
the build.

DynaTest's `group { … }` / `test { … }` become `@Nested` / `@Test`, and its
helpers get Java equivalents in `TestSerialization` and `TestAssertions`.
`LocatorDsl.kt` existed only so Kotlin callers could write
`_get<Button> { caption = "…" }`; the tests now call `Locator._get(clazz,
spec -> …)`, which is what a user writes anyway. `AllTests.kt` was a pure
aggregator and disappears.

Test counts are unchanged across all five suites, verified by comparing the
surefire testcase names rather than the totals.
`shared` now builds clean under doclint, errors and warnings both, so
`<doclint>none</doclint>` is gone from its pom.

The "roughly 200 issues" that setting was added for was javadoc's output
cap, not the count: the tool stops after 100 errors and 100 warnings. The
real number was 935. The root pom now passes `-Xmaxerrs`/`-Xmaxwarns` so
the next reader gets a count rather than a ceiling.

Most of it was missing `@param`, `@return` and member descriptions in the
files the Kotlin port rewrote, since KDoc carries none of those tags. The
genuine defects were unresolvable `@link` and `@throws` targets, `<p/>`
and empty `<p>`, and markup that javadoc read as HTML.

`locator-processor` accounted for 291 of them, because it copies a tester
method's Javadoc into the generated `*Locator`. It now documents the
generated class's type parameters and both constructors, and rewrites a
copied `{@link #member}` to point back at the tester. The generated file
has no imports, so a tester has to spell cross-package `@link` and
`@throws` targets out in full; that rule is now in the guidelines.

Also converts the Javadoc the port left in KDoc markup, `[TreeGrid]` and
`*skip*`, to `{@link}`, `{@code}` and `<em>`.
The whole reactor now builds clean under doclint, errors and warnings
both.

Most of the 35 were the `AbstractBrowserlessExtension` builder helpers
and the undocumented default constructor of every extension, test base,
lookup initializer and security customizer; those classes now declare an
explicit constructor. The rest were the public fields and constructors of
the Spring mocks.

Also replaces a `{@link #reload()}` that pointed into a package-private
class, which javadoc cannot link to, with `{@code reload()}`.
`MockSpringServlet.routes`, `.ctx` and `.uiFactory` were public final
fields. They are private now, behind `getRoutes()`,
`getApplicationContext()` and `getUiFactory()` — the shape
`MockVaadinServlet`, `MockService` and `MockVaadinSession` already use.
Reading `servlet.routes`, `servlet.ctx` or `servlet.uiFactory` stops
compiling; call the getter instead. There were no such callers in this
repository, and the class is marked internal.

`SessionObjects` gets the same treatment, but there it is a regression
fix rather than a change: it was a Kotlin `data class`, so the released
artifact exposes private fields plus `getSession()` and friends. The port
to Java flattened the four `val`s into public fields while keeping the
getters, which added API no release ever had. Its compiled surface now
matches 1.2.0-beta1 again.
`GridContextMenuTester` and `GridTester.contextMenu` landed while
`shared` still had `<doclint>none</doclint>`, so their Javadoc was never
checked: 25 `<p/>` tags, which propagate into four generated `*Locator`
files, and a `{@link GridContextMenuTester#open()}` that does not resolve
once copied into a generated locator, since that file has no imports.

Also documents `GridUtils._internalId`, which became public for
`GridContextMenuSupport` to call from another package.
`MockVaadinTest.kt` carried five `/* TODO: uncomment after importing
Locator APIs */` blocks, eight tests in all, and the port to Java dropped
them silently. They were not running, so the testcase-name check that
guarded the rest of the port could not see them.

The Java `Locator` they were waiting on exists now, so they are back:
navigation in a mocked environment, `beforeClientResponse` running once
per lookup, a dialog's contents being reachable while it is open, reload
re-navigating to the current URL, and the thread-pool example.
The conflict is javadoc in `GridTester.getCellText`: `main` rewrote the
paragraph for the new `getCellComponent`, this branch had reflowed the
same paragraph for doclint. Kept `main`'s wording with `<p>`.

The `<p/>` that `main` added to `TextFieldTester` and `TextAreaTester`
merged cleanly but no longer builds under doclint, since this branch
dropped `<doclint>none</doclint>` from `shared`, so it is `<p>` here too.
The focus and blur simulation merged from main still called the Kotlin
facade `PrettyPrintTreeKt`, which no longer exists on this branch.
@Artur-

Artur- commented Sep 22, 2026

Copy link
Copy Markdown
Member

What impact would this change have on Kotlin users?

@mcollovati

mcollovati commented Sep 22, 2026

Copy link
Copy Markdown
Contributor Author

It will for sure break compilation if they are using internal APIs or Kotlin extension methods like Component.toPrettyString().
In both cases it should be possible to replace the missing parts calling the equivalent Java methods.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants