1. Overview

Java and Kotlin are languages designed for the JVM. However, if we were to try using Kotlin in a Java EE container, we’d immediately run into some idiomatic challenges.

In this tutorial, we’ll take a look at those challenges and how to address them effectively.

2. Challenges

Java and Kotlin are somewhat different languages. The syntax is different, which is obvious, but that is not the real issue. Kotlin’s language design and paradigm differ, which creates a few problems when using it within Java EE containers. We need to meet these differences to build enterprise applications with Kotlin.

For example, Kotlin classes are final by default. To make them extensible, we need to open them explicitly. We also need to provide parameterless constructors for use with frameworks such as JPA or Jackson. In Java, they’re available by default, but in Kotlin, we need to do some extra work. The injection, which containers use heavily, will also be a bit tricky because of initialization, as will the integration testing with Arquillian.

Let’s address these challenges. Our example will demonstrate how to build a simple CRUD application in Kotlin and run it in a Java EE container. We’ll start with a simple data class and then write services and integration tests.

3. Dependencies

So, first things first, let’s add the jakarta.jakartaee-api dependency:

<dependency>
    <groupId>jakarta.platform</groupId>
    <artifactId>jakarta.jakartaee-api</artifactId>
    <version>11.0.0-M1</version>
    <scope>provided</scope>
</dependency>

Note that we’re setting this dependency as provided since we only need it for compilation.

4. JPA Entity

Now, let’s write a simple Student data class which we’ll use as an entity and as a DTO at the same time.

We’ll need the student’s id, first and last name:

@Entity
data class Student constructor (

    @SequenceGenerator(name = "student_id_seq", sequenceName = "student_id_seq",
      allocationSize = 1)
    @GeneratedValue(generator = "student_id_seq", strategy = GenerationType.SEQUENCE)
    @Id
    var id: Long?,

    var firstName: String,
    var lastName: String

) {
    constructor() : this(null, "", "")
    constructor(firstName: String, lastName: String) : this(null, firstName, lastName)
}

We’ve defined Student as a data class, which is a special kind of class in Kotlin for holding data. In these classes, the compiler automatically derives common functions such as equals(), hashCode(), and toString() from all properties declared in the primary constructor.

The automatic generation of functions makes data classes very convenient and easy to use. However, they also have to fulfill some rules. For example, they have to have a primary constructor with at least one parameter marked as val or var.

In our data class, we defined all members inside the primary constructor.

We have also defined two secondary constructors:

  • First, we have a parameterless constructor, which the container and common frameworks such as JPA or Jackson require to instantiate the class and populate the data with setters.
  • Second, we have a convenience constructor, which we use when we want to instantiate a new object without an ID. We typically use it when we want to save a new entity to the database.

Additionally, we use standard JPA annotations to define the JPA entity, just as we do in standard Java EE.

3. Business Service

We need a business service to handle the CRUD operations with an EntityManager. It’s simple and very similar to Java implementation, with a few notable differences:

@Stateless
open class StudentService {

    @PersistenceContext
    private lateinit var entityManager: EntityManager

    open fun create(student: Student) = entityManager.persist(student)

    open fun read(id: Long): Student? = entityManager.find(Student::class.java, id)

    open fun update(student: Student) = entityManager.merge(student)

    open fun delete(id: Long) = entityManager.remove(read(id))
}

Since all classes in Kotlin are final, we need to explicitly open our class to enable the extension. Java EE container needs the extension because it’ll create a proxy from our service class and inject it where it’s required. Public methods also have to be open otherwise the proxy cannot be created. Therefore, we use the open keyword on the class and all its public methods.

EntityManager is used in a similar way as in Java with the @PersistenceContext annotation. We define it as a private member with an additional lateinit keyword. This keyword tells the compiler that this variable is null at first, but that it’ll be initialized before the first use. This eliminates unnecessary null checks and aligns perfectly with container injection. We’ll use it every time we use the @Inject annotation.

4. REST Service

Finally, we need to define a REST service endpoint for our application. To do that, we need to register our resource class:

@ApplicationPath("/")
class ApplicationConfig : Application() {
    override fun getClasses() = setOf(StudentResource::class.java)
}

Now, we define our resource class. It’ll be very close to what we’d do in Java, with a few Kotlin-specific modifications:

@Path("/student")
open class StudentResource {

    @Inject
    private lateinit var service: StudentService

    @POST
    open fun create(student: Student): Response {
        service.create(student)
        return Response.ok().build()
    }

    @GET
    @Path("/{id}")
    open fun read(@PathParam("id") id: Long): Response {
        val student  = service.read(id)
        return Response.ok(student, MediaType.APPLICATION_JSON_TYPE).build()
    }

    @PUT
    @Path("/{id}")
    open fun update(@PathParam("id") id: Long, student: Student): Response {
        service.update(student)
        return Response.ok(student, MediaType.APPLICATION_JSON_TYPE).build()
    }

    @DELETE
    @Path("/{id}")
    open fun delete(@PathParam("id") id: Long): Response {
        service.delete(id)
        return Response.noContent().build()
    }

}

We explicitly opened our resource class and all the public methods, like in the previous example with the business service. We also used the lateinit keyword again, this time for the injection of our business service into the resource class.

5. Testing With Arquillian

We’ll now implement the integration testing of our application with Arquillian and Shrinkwrap.

To test our application with Arquillian, we need to set up the packaging and the deployment to the test container with ShrinkWrap:

@RunWith(Arquillian.class)
public class StudentResourceIntegrationTest {

    @Deployment
    public static WebArchive createDeployment() {
        JavaArchive[] kotlinRuntime = Maven.configureResolver()
          .workOffline()
          .withMavenCentralRepo(true)
          .withClassPathResolution(true)
          .loadPomFromFile("pom.xml")
          .resolve("org.jetbrains.kotlin:kotlin-stdlib")
          .withTransitivity()
          .as(JavaArchive.class);

        return ShrinkWrap.create(WebArchive.class, "kotlin.war")
          .addPackages(true, Filters.exclude(".*Test*"), "com.baeldung.jeekotlin")
          .addAsLibraries(kotlinRuntime)
          .addAsResource("META-INF/persistence.xml")
          .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml");
    }
    // more code
}

The deployment is similar to typical Java deployments for integration testing, but it contains some additional configuration. Here, we use the Shrinkwrap Maven Resolver to retrieve the kotlin-stdlib from the Maven repository. We then archive it to our WAR as a library.

After that, we use the HttpClient to run CRUD requests against our REST API:

@Test
@RunAsClient
public void when_post__then_return_ok(@ArquillianResource URL url)
  throws URISyntaxException, JsonProcessingException {
    String student = new ObjectMapper()
      .writeValueAsString(new Student("firstName", "lastName"));
      WebTarget webTarget = ClientBuilder.newClient().target(url.toURI());

    Response response = webTarget
      .path("/student")
      .request(MediaType.APPLICATION_JSON)
      .post(Entity.json(student));

    assertEquals(200, response.getStatus());
}

In this example, we use @ArquillianResource to provide the URL for the API, then we serialize the Student object and POST it to the API. If everything is okay and the object has been created in the database, the response status is 200 OK, which we assert at the end of the test.

6. Conclusion

In this article, we demonstrated how to build CRUD REST JPA application in Kotlin, how to deploy it, how to run it in the Java EE container and how to test it with Arquillian. As you can see, Kotlin and Java work well together but we have to do some additional work to make it happen.

The complete source code for the example is available over on GitHub.

Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.