All skills
kotlin avatar

/kotlin-tooling-java-to-kotlin

@8fc58af official
by kotlinkotlin/kotlin-agent-skills1.1k stars
42

Use when converting Java source files to idiomatic Kotlin, when user mentions "java to kotlin", "j2k", "convert java", "migrate java to kotlin", or when working with .java files that need to become .kt files. Handles framework-aware conversion for Spring, Lombok, Hibernate, Jackson, Micronaut, Quarkus, Dagger/Hilt, RxJava, JUnit, Guice, Retrofit, and Mockito.

Use this Skill: https://skilld.dev/gh/kotlin/kotlin-agent-skills/kotlin-tooling-java-to-kotlin

This session only. Nothing lands on disk.

referencesframeworksRXJAVA.md

≈1.4k tokens on demand. Your agent reads this file only when SKILL.md points to it.

RxJava to Coroutines/Flow Conversion Guide

When This Applies

Detected when imports match io.reactivex.* or rx.*. This is a significant paradigm shift — RxJava reactive types map to Kotlin coroutines and Flow.

Key Rules

1. Dependency setup

Add kotlinx-coroutines-core and kotlinx-coroutines-rx3 (or kotlinx-coroutines-rx2) as dependencies if performing a gradual migration. The bridge library provides extension functions like asFlow() and asObservable() for interop at module boundaries.

2. Type mapping

RxJava Kotlin
Observable<T> Flow<T>
Flowable<T> Flow<T> (backpressure is built-in)
Single<T> suspend fun: T
Maybe<T> suspend fun: T?
Completable suspend fun returning Unit
Disposable Job (from coroutines)
CompositeDisposable CoroutineScope (structured concurrency)

3. Operator mapping

RxJava Kotlin Flow
subscribeOn(Schedulers.io()) flowOn(Dispatchers.IO)
observeOn(AndroidSchedulers.mainThread()) flowOn(Dispatchers.Main) or collect on Main
flatMap flatMapMerge or flatMapConcat
map map (same)
filter filter (same)
zip combine or zip
merge merge
concat flatMapConcat
onErrorReturn catch { emit(default) }
doOnNext onEach
subscribe() collect {} in a coroutine scope

4. Error handling

RxJava's onError callback maps to Flow's catch operator or a try-catch block wrapping the collect call. In suspend functions (replacing Single/Completable), use standard try-catch.

5. Backpressure

Flow has built-in backpressure via suspension. There is no need for a separate Flowable type — all Flow instances support backpressure by default.

6. Threading

flowOn changes the upstream dispatcher (analogous to subscribeOn). Collection always happens on the caller's dispatcher. To collect on a specific dispatcher, launch the collecting coroutine in the desired scope.

7. Lifecycle and cancellation

RxJava's Disposable / CompositeDisposable pattern is replaced by structured concurrency. Cancelling a CoroutineScope cancels all child coroutines and flow collections automatically.


Example: Converting an Observable Chain to Flow

Java Input

package com.acme.data;

import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.schedulers.Schedulers;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.disposables.CompositeDisposable;

/**
 * Repository that streams user data from a remote source.
 */
public class UserRepository {

    private final UserApi api;
    private final CompositeDisposable disposables = new CompositeDisposable();

    public UserRepository(UserApi api) {
        this.api = api;
    }

    public Observable<List<User>> getActiveUsers() {
        return api.getAllUsers()
                .subscribeOn(Schedulers.io())
                .map(users -> filterActive(users))
                .doOnNext(users -> logCount(users))
                .onErrorReturn(throwable -> Collections.emptyList());
    }

    public void observeUsers(UserCallback callback) {
        disposables.add(
            getActiveUsers()
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                    users -> callback.onUsers(users),
                    error -> callback.onError(error)
                )
        );
    }

    public void clear() {
        disposables.clear();
    }

    private List<User> filterActive(List<User> users) {
        return users.stream().filter(User::isActive).collect(Collectors.toList());
    }

    private void logCount(List<User> users) {
        System.out.println("Active users: " + users.size());
    }
}

Kotlin Output

package com.acme.data

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch

/**
 * Repository that streams user data from a remote source.
 */
class UserRepository(
    private val api: UserApi
) {
    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)

    fun getActiveUsers(): Flow<List<User>> =
        api.getAllUsers()
            .map { users -> users.filter { it.isActive } }
            .onEach { users -> println("Active users: ${users.size}") }
            .catch { emit(emptyList()) }
            .flowOn(Dispatchers.IO)

    fun observeUsers(callback: UserCallback) {
        scope.launch {
            getActiveUsers().collect { users ->
                callback.onUsers(users)
            }
        }
    }

    fun clear() {
        scope.cancel()
    }
}

Key points:

  • Observable<List<User>> becomes Flow<List<User>>.
  • subscribeOn(Schedulers.io()) becomes flowOn(Dispatchers.IO) at the end of the chain (it affects all upstream operators).
  • CompositeDisposable is replaced by a CoroutineScope with SupervisorJob. Calling scope.cancel() cancels all active collections.
  • doOnNext becomes onEach.
  • onErrorReturn becomes catch { emit(emptyList()) }.
  • observeOn(AndroidSchedulers.mainThread()) is unnecessary because scope already uses Dispatchers.Main, and collect runs on the collector's dispatcher.
  • Java streams (filter + collect) become Kotlin's filter directly on the list.

Source: SKILL.md on GitHub

No alerts16d4 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    This skill provides a methodology for converting Java source code to Kotlin. While the logic is sound and focused on development tasks, it possesses an indirect prompt injection surface by processing untrusted source code and performing file system operations.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at 8fc58af. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 3 weeks ago.

Activeupdated 7 months ago
metadata
{
  "author": "JetBrains",
  "version": "1.0.0"
}
  • kotlin
  • java
  • migration
  • spring
  • hibernate
  • jackson
  • junit
  • rxjava
  • retrofit
  • mockito
  • conversion

README badge

README badge for kotlin/kotlin-agent-skills/kotlin-tooling-java-to-kotlin

Converts Java source files to idiomatic Kotlin using a structured 4-step methodology with framework-aware handling for Spring, Lombok, Hibernate, Jackson, Micronaut, Quarkus, Dagger/Hilt, RxJava, JUnit, Guice, Retrofit, and Mockito. Preserves git history through two-phase renames and supports batch conversion with dependency ordering.

Generated from the current SKILL.md.

What frameworks does this skill handle?
Spring, Lombok, Hibernate, Jackson, Micronaut, Quarkus, Dagger/Hilt, RxJava, JUnit, Guice, Retrofit, and Mockito. The skill detects which frameworks are in use by scanning import statements and loads only the relevant framework guides.
Does this skill preserve git blame history?
Yes. It uses a two-phase approach: first rename the file with `git mv`, then replace the content in a separate commit so the rename is tracked separately from the conversion changes.
Can this skill convert multiple Java files at once?
Yes. For batch conversions, it sorts files by dependency order (leaf files first), converts one at a time through the full workflow, and updates cross-references as needed.
What happens if conversion fails the verification step?
The skill reverts to the previous conversion step and retries. It checks five invariants at each step and verifies output by compilation, tests, and annotation site targets.
Does this handle Java interop edge cases like platform types and checked exceptions?
Yes. The skill documents and handles Kotlin keyword conflicts, SAM conversion ambiguity, platform types, @JvmStatic/@JvmField/@JvmOverloads, checked exceptions, and wildcard generics in a separate known-issues reference.

Generated from the current SKILL.md. These answers refresh after source changes.