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.

referencesframeworksHIBERNATE.md

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

Hibernate / JPA Conversion Guide

When This Applies

Detected when imports match any of:

  • javax.persistence.*
  • jakarta.persistence.*
  • org.hibernate.*

Critical Rules

  1. Do NOT use data classes for JPA entities. Data classes generate equals/hashCode based on all properties, which breaks Hibernate's identity semantics and proxy creation.

  2. Keep entity classes open. Hibernate creates proxies via subclassing. Kotlin classes are final by default, so you must use open explicitly (or use the allopen compiler plugin with JPA annotation support).

  3. Provide a no-argument constructor if Hibernate requires one for proxy creation. Use a secondary constructor or default values for all primary constructor parameters.

  4. Annotation site targets matter:

    • @Id, @Column, @GeneratedValue on fields → use @field:Id, @field:Column, etc. in Kotlin, OR place annotations on constructor parameters with @field: site target.
    • @ManyToOne, @OneToMany, @JoinColumn → same @field: targeting.
  5. Lazy loading considerations: @ManyToOne(fetch = FetchType.LAZY) requires the entity class to be open for proxy creation. @OneToMany with lazy collections work with Kotlin's MutableList.

  6. @Embeddable classes: Can be data classes (they don't need proxies).

  7. @MappedSuperclass: Must be open abstract class in Kotlin.

Examples

Example 1: JPA Entity with @Id, @Column, and Relationships

Java:

@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "username", nullable = false, unique = true)
    private String username;

    @Column(name = "email")
    private String email;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "department_id")
    private Department department;

    protected User() {}

    public User(String username, String email, Department department) {
        this.username = username;
        this.email = email;
        this.department = department;
    }

    public Long getId() { return id; }
    public String getUsername() { return username; }
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
    public Department getDepartment() { return department; }
    public void setDepartment(Department department) { this.department = department; }
}

Kotlin:

@Entity
@Table(name = "users")
open class User(

    @field:Column(name = "username", nullable = false, unique = true)
    open val username: String,

    @field:Column(name = "email")
    open var email: String? = null,

    @field:ManyToOne(fetch = FetchType.LAZY)
    @field:JoinColumn(name = "department_id")
    open var department: Department? = null

) {
    @field:Id
    @field:GeneratedValue(strategy = GenerationType.IDENTITY)
    open var id: Long? = null
        protected set

    protected constructor() : this(username = "")
}

Example 2: @Embeddable Value Object

Java:

@Embeddable
public class Address {

    @Column(name = "street")
    private String street;

    @Column(name = "city")
    private String city;

    @Column(name = "zip_code")
    private String zipCode;

    protected Address() {}

    public Address(String street, String city, String zipCode) {
        this.street = street;
        this.city = city;
        this.zipCode = zipCode;
    }

    public String getStreet() { return street; }
    public String getCity() { return city; }
    public String getZipCode() { return zipCode; }
}

Kotlin:

@Embeddable
data class Address(

    @field:Column(name = "street")
    val street: String = "",

    @field:Column(name = "city")
    val city: String = "",

    @field:Column(name = "zip_code")
    val zipCode: String = ""
)

@Embeddable classes can safely be data classes because Hibernate does not proxy them. Default values satisfy the no-arg constructor requirement.

Example 3: Entity with @ManyToOne and @OneToMany

Java:

@Entity
@Table(name = "departments")
public class Department {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "name", nullable = false)
    private String name;

    @OneToMany(mappedBy = "department", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<User> users = new ArrayList<>();

    protected Department() {}

    public Department(String name) {
        this.name = name;
    }

    public Long getId() { return id; }
    public String getName() { return name; }
    public List<User> getUsers() { return users; }

    public void addUser(User user) {
        users.add(user);
        user.setDepartment(this);
    }

    public void removeUser(User user) {
        users.remove(user);
        user.setDepartment(null);
    }
}

Kotlin:

@Entity
@Table(name = "departments")
open class Department(

    @field:Column(name = "name", nullable = false)
    open val name: String = ""

) {
    @field:Id
    @field:GeneratedValue(strategy = GenerationType.IDENTITY)
    open var id: Long? = null
        protected set

    @field:OneToMany(mappedBy = "department", cascade = [CascadeType.ALL], orphanRemoval = true)
    open val users: MutableList<User> = mutableListOf()

    protected constructor() : this(name = "")

    fun addUser(user: User) {
        users.add(user)
        user.department = this
    }

    fun removeUser(user: User) {
        users.remove(user)
        user.department = null
    }
}

Key points in this example:

  • cascade array syntax uses Kotlin's [CascadeType.ALL] instead of Java's {CascadeType.ALL}.
  • The collection is typed as MutableList to allow Hibernate to manage the relationship.
  • The class and its properties are open so Hibernate can create proxies.
  • The no-arg constructor delegates to the primary constructor with default values.

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.