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.

referencesframeworksSPRING.md

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

Spring Framework Conversion Guide

When This Applies

This guide applies when the Java source contains imports matching org.springframework.*. This covers Spring Boot, Spring MVC, Spring Data, and Spring Security.

Key Rules

1. SpringApplication.run — spread CLI args

In Kotlin, String[] varargs must be spread with the * operator.

  • Java: SpringApplication.run(App.class, args);
  • Kotlin: SpringApplication.run(App::class.java, *args)

2. Constructor injection over @Autowired

Kotlin's primary constructor makes constructor injection natural. When a class has a single constructor, Spring auto-discovers it — remove @Autowired.

3. Stereotype annotations

@Component, @Service, @RestController, and @Repository target the class. Preserve these annotations exactly. No annotation site target is needed.

4. @Value annotation

Use @Value on constructor parameters. Escape $ in SpEL expressions to prevent Kotlin string template interpretation:

@Value("\${app.name}") val appName: String

5. @ConfigurationProperties

Convert to a data class only if the properties are immutable. For mutable configuration, use a regular class with lateinit var.

6. Spring Data repositories

Interface declarations convert directly. Replace Optional<T> return types with nullable T? in Kotlin for idiomatic usage.

7. @RequestMapping / @GetMapping / @PostMapping etc.

Preserve exactly. Where Java uses array initializer syntax for annotation parameters, use arrayOf() in Kotlin.

8. @Transactional

Preserve exactly. The class must remain open because Spring creates proxies via subclassing. Do not make @Transactional classes final.

9. @Bean methods in @Configuration classes

@Bean methods must be open so that Spring can override them in CGLIB proxies. Alternatively, apply the allopen compiler plugin with a Spring preset, which makes annotated classes and their members open automatically.


Examples

Example 1: Spring Boot Application Main Class

Java:

package com.acme;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Kotlin:

package com.acme

import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication

@SpringBootApplication
open class Application

fun main(args: Array<String>) {
    runApplication<Application>(*args)
}

Key changes:

  • main becomes a top-level function (no companion object needed).
  • runApplication<T> is a Spring Boot Kotlin extension that replaces SpringApplication.run(T::class.java, *args).
  • The *args spread operator is required for the varargs parameter.

Example 2: REST Controller with Constructor Injection

Java:

package com.acme.web;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/users")
public class UserController {

    private final UserService userService;

    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping("/{id}")
    public UserDto getUser(@PathVariable Long id) {
        return userService.findById(id);
    }

    @GetMapping
    public List<UserDto> getAllUsers() {
        return userService.findAll();
    }
}

Kotlin:

package com.acme.web

import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@RestController
@RequestMapping("/api/users")
class UserController(
    private val userService: UserService
) {

    @GetMapping("/{id}")
    fun getUser(@PathVariable id: Long): UserDto? {
        return userService.findById(id)
    }

    @GetMapping
    fun getAllUsers(): List<UserDto> {
        return userService.findAll()
    }
}

Key changes:

  • @Autowired is removed — Spring auto-discovers the single constructor.
  • The Autowired import is removed because it is no longer referenced.
  • Constructor parameter becomes a private val in the primary constructor.
  • Return type UserDto becomes UserDto? where the service may return null.

Example 3: @ConfigurationProperties Class

Java:

package com.acme.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "app.mail")
public class MailProperties {

    private String host;
    private int port = 587;
    private String username;
    private String password;

    public String getHost() { return host; }
    public void setHost(String host) { this.host = host; }

    public int getPort() { return port; }
    public void setPort(int port) { this.port = port; }

    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }

    public String getPassword() { return password; }
    public void setPassword(String password) { this.password = password; }
}

Kotlin (mutable config with lateinit var):

package com.acme.config

import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.stereotype.Component

@Component
@ConfigurationProperties(prefix = "app.mail")
open class MailProperties {
    lateinit var host: String
    var port: Int = 587
    lateinit var username: String
    lateinit var password: String
}

Key changes:

  • Getters and setters are replaced by Kotlin properties.
  • lateinit var is used for required String properties that Spring populates after construction.
  • port keeps its default value and uses a regular var (lateinit does not support primitive types).
  • The class is open so that Spring can create a CGLIB proxy for it.

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.