DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
  1. DZone
  2. Coding
  3. Languages
  4. The State of Kotlin: Using Jakarta EE/MicroProfile in Traditional Applications

The State of Kotlin: Using Jakarta EE/MicroProfile in Traditional Applications

Explore Kotlin and how it can be implemented with Jakarta EE and MicroProfile.

Víctor Orozco user avatar by
Víctor Orozco
·
Apr. 24, 19 · Presentation
Like (4)
Save
Tweet
Share
13.30K Views

Join the DZone community and get the full member experience.

Join For Free

Recently, I had the opportunity to catch up with some R&D tasks in my main job. In short, I had to evaluate the feasibility of using Kotlin in regular Jakarta EE/MicroProfile projects including:

  • Language benefits
  • Jakarta EE/MicroProfile API support, specifically CDI, JPA, JAX-RS
  • Maven support
  • Compatibility with my regular libraries (Flyway, SL4J, Arquillian)
  • Regular application server support, e.g Payara
  • Tooling support (deployment, debugging, project import)
  • Cloud deployment by Docker

TL;DR Version

Kotlin is a great language and plays well with Maven. While I'm able to use many of the EE API for "services," however, the roadblock is not the language or libraries but the tooling support.

The experience is superb on IntelliJ IDEA and it all works as expected. However, the IDE is a barrier, and if you're not an IntelliJ user, Kotlin doesn't play well with WTP on Eclipse (hence, it doesn't deploy to app servers) and Kotlin support for Netbeans is mostly dead.

About the Language

If you have a couple of years in the JVM space, you probably remember that Scala, Ceylon, and Kotlin have been considered as better forms of Java. I do a lot of different types of development in different languages, like Java for back-end development, Kotlin for mobile development, JavaScript for front-end development, and Bash for almost every automation task, so I know the strengths of Kotlin in the mobile space where it is especially important. Essentially, most Java development on Android is currently a Java 7+ Lambdas experience.

My top five features that could help you in your EE tasks are:

  • One line functions
  • Public by default
  • Multiline Strings
  • Companion objects
  • Type inference

I'll try to exemplify these in a regular application

The Jakarta EE/MicroProfile application

The demo application follows a simple structure; it includes MicroProfile Config, CDI, EJB, JPA, and JAX-RS, focused on a simple phrase collector/retrieval service. Some interesting Kotlin features are highlighted below.

Source code is available at my GitHub repo.

Project Configuration

To enable Kotlin support, I basically followed Kotlin's Maven guide, which is not so explanatory, but if you have a little bit of experience in Maven, this won't be a problem.

Besides adding Kotlin dependencies to a regular EE pom.xml, a special configuration is needed for the all-open plugin. Additionally, Jakarta EE works with proxy entities that inherit from your original code. To make it simple, all CDI, EJB, and JAX-RS annotations were included as "open activators."

<plugin>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-maven-plugin</artifactId>
    <version>${kotlin.version}</version>
    <executions>
        <execution>
            <id>compile</id>
            <goals> <goal>compile</goal> </goals>
            <configuration>
                <sourceDirs>
                    <sourceDir>${project.basedir}/src/main/kotlin</sourceDir>
                    <sourceDir>${project.basedir}/src/main/java</sourceDir>
                </sourceDirs>
            </configuration>
        </execution>
        <execution>
            <id>test-compile</id>
            <goals> <goal>test-compile</goal> </goals>
            <configuration>
                <sourceDirs>
                    <sourceDir>${project.basedir}/src/test/kotlin</sourceDir>
                    <sourceDir>${project.basedir}/src/test/java</sourceDir>
                </sourceDirs>
            </configuration>
        </execution>
    </executions>
    <configuration>
        <compilerPlugins>
            <plugin>all-open</plugin>
        </compilerPlugins>

        <pluginOptions>
            <option>all-open:annotation=javax.ws.rs.Path</option>
            <option>all-open:annotation=javax.enterprise.context.RequestScoped</option>
            <option>all-open:annotation=javax.enterprise.context.SessionScoped</option>
            <option>all-open:annotation=javax.enterprise.context.ApplicationScoped</option>
            <option>all-open:annotation=javax.enterprise.context.Dependent</option>
            <option>all-open:annotation=javax.ejb.Singleton</option>
            <option>all-open:annotation=javax.ejb.Stateful</option>
            <option>all-open:annotation=javax.ejb.Stateless</option>
        </pluginOptions>
    </configuration>

    <dependencies>
        <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-maven-allopen</artifactId>
            <version>${kotlin.version}</version>
        </dependency>
    </dependencies>
</plugin>


Model

Table models are easily created by using Kotlin's data classes, note the default values on parameters and nullable types for autogenerated values, using an incrementable value on a table.

@Entity
@Table(name = "adm_phrase")
@TableGenerator(name = "admPhraseIdGenerator", table = "adm_sequence_generator", pkColumnName = "id_sequence", valueColumnName = "sequence_value", pkColumnValue = "adm_phrase", allocationSize = 1)
data class AdmPhrase(
        @Id
        @GeneratedValue(strategy = GenerationType.TABLE, generator = "admPhraseIdGenerator")
        @Column(name = "phrase_id")
        var phraseId:Long? = null,
        var author:String = "",
        var phrase:String = ""
)


After that, I also need to provide a repository; the repository is a classic CRUD component injectable with CDI, and one line methods are created to make the repository concise. The interesting part, however, is that Kotlin's nullability system plays well with CDI by using lateinit.

The most pleasant part is to create JPQL queries with multiline declarations. In general, I dislike the +  signs polluting my query in Java.

@RequestScoped
class AdmPhraseRepository @Inject constructor() {

    @Inject
    private lateinit var em:EntityManager

    @PostConstruct
    fun init() {
        println ("Booting repository")
    }

    fun create(admPhrase:AdmPhrase) = em.persist(admPhrase)

    fun update(admPhrase:AdmPhrase) = em.merge(admPhrase)

    fun findById(phraseId: Long) = em.find(AdmPhrase::class.java, phraseId)

    fun delete(admPhrase: AdmPhrase) = em.remove(admPhrase)

    fun listAll(author: String, phrase: String): List<AdmPhrase> {

        val query = """SELECT p FROM AdmPhrase p
            where p.author LIKE :author
            and p.phrase LIKE :phrase
        """

        return em.createQuery(query, AdmPhrase::class.java)
                .setParameter("author", "%$author%")
                .setParameter("phrase", "%$phrase%")
                .resultList
    }

}


Controller

The model needs to be exposed by using a controller, hence a JAX-RS activator is needed:

@ApplicationPath("/rest")
class RestApplication : Application()


That's all the code.

On the other side, implementing the controller looks a lot more like Java, especially if the right HTTP codes are needed. In this line, to express the Java class to the builders, the special Kotlin syntax this::class.java is mandatory.

We also observed the elvis operator in action (inDELETE). If the entity is not found, the alternative return is fired immediately, which is a nice idiomatic resource.

@Path("/phrases")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
class AdmPhraseController{

    @Inject
    private lateinit var admPhraseRepository: AdmPhraseRepository

    @Inject
    private lateinit var logger: Logger

    @GET
    fun findAll(@QueryParam("author") @DefaultValue("%") author: String,
                @QueryParam("phrase") @DefaultValue("%") phrase: String) =
                admPhraseRepository.listAll(author, phrase)

    @GET
    @Path("/{id:[0-9][0-9]*}")
    fun findById(@PathParam("id") id:Long) = admPhraseRepository.findById(id)

    @PUT
    fun create(phrase: AdmPhrase): Response {
        admPhraseRepository.create(phrase)
        return Response.created(UriBuilder.fromResource(this::class.java)
                .path(phrase.phraseId.toString()).build()).build()
    }

    @POST
    @Path("/{id:[0-9][0-9]*}")
    fun update(@PathParam("id") id: Long?, phrase: AdmPhrase): Response {
        if(id != phrase.phraseId) return Response.status(Response.Status.NOT_FOUND).build()

        val updatedEntity = admPhraseRepository.update(phrase)
        return Response.ok(updatedEntity).build()
    }

    @DELETE
    @Path("/{id:[0-9][0-9]*}")
    fun delete(@PathParam("id") id: Long): Response {
        val updatedEntity = admPhraseRepository.findById(id) ?:
            return Response.status(Response.Status.NOT_FOUND).build()
        admPhraseRepository.delete(updatedEntity)
        return Response.ok().build()
    }
}


To try MicroProfile, a second controller is created that tries to read JAVA_HOME:

@Path("/hello")
class HelloController{

    @Inject
    @ConfigProperty(name ="JAVA_HOME", defaultValue = "JAVA_HOME")
    lateinit var javaHome:String

    @GET
    fun doHello() = "There is no place like $javaHome"

}

Utilities

To create a real test, four "advanced components" were included, being an entity manager producer for CDI components, a Flyway bootstrapper with @Startup EJB, a Log producer for SL4J, and a "simple" test with Arquillian.

The producer itself is pretty similar to its Java equivalent, it only takes advantage of one line methods.

@ApplicationScoped
class EntityManagerProducer {

    @PersistenceUnit
    private lateinit var entityManagerFactory: EntityManagerFactory

    @Produces
    @Default
    @RequestScoped
    fun create(): EntityManager = this.entityManagerFactory.createEntityManager()

    fun dispose(@Disposes @Default entityManager: EntityManager) {
        if (entityManager.isOpen) {
            entityManager.close()
        }
    }
}


The Flyway migration is implemented with EJB to fire it every time the application is deployed (and, of course, to test EJB). Since Kotlin doesn't have a try-with-resources structure, the resource management is implemented with a let block, making it really readable. Besides this, if there is a problem with the data source, it will be null and the let block won't be executed.

@ApplicationScoped
@Singleton
@Startup
class FlywayBootstrapper{

    @Inject
    private lateinit var logger:Logger

    @Throws(EJBException::class)
    @PostConstruct
    fun init() {

        val ctx = InitialContext()
        val dataSource = ctx.lookup("java:app/jdbc/integrumdb") as? DataSource

        dataSource.let {
            val flywayConfig = Flyway.configure()
                    .dataSource(it)
                    .locations("db/postgresql")

            val flyway = flywayConfig.load()
            val migrationInfo = flyway.info().current()

            if (migrationInfo == null) {
                logger.info("No existing database at the actual datasource")
            }
            else {
                logger.info("Found a database with the version: ${migrationInfo.version} : ${migrationInfo.description}")
            }

            flyway.migrate()
            logger.info("Successfully migrated to database version: {}", flyway.info().current().version)
            it?.connection?.close()
        }
        ctx.close()
    }
}


To create a non-empty-database, a PostgreSQL migration was created at db/postgresql in project resources.

CREATE TABLE IF NOT EXISTS public.adm_sequence_generator (
    id_sequence VARCHAR(75) DEFAULT '' NOT NULL,
    sequence_value BIGINT DEFAULT 0 NOT NULL,
    CONSTRAINT adm_sequence_generator_pk PRIMARY KEY (id_sequence)
);
COMMENT ON COLUMN public.adm_sequence_generator.id_sequence IS 'normal_text - people name, items short name';
COMMENT ON COLUMN public.adm_sequence_generator.sequence_value IS 'integuer_qty - sequences, big integer qty';


CREATE TABLE IF NOT EXISTS public.adm_phrase
(
    phrase_id BIGINT DEFAULT 0 NOT NULL,
    author varchar(25) DEFAULT '' NOT NULL,
    phrase varchar(25) DEFAULT '' NOT NULL,
    CONSTRAINT adm_phrase_pk PRIMARY KEY (phrase_id)
);

insert into adm_phrase values (1, 'Twitter','Kotlin is cool');
insert into adm_phrase values (2, 'TIOBE','Java is the king');


Log producer also benefits from one line methods:

open class LogProducer{

    @Produces
    fun produceLog(injectionPoint: InjectionPoint): Logger =
            LoggerFactory.getLogger(injectionPoint.member.declaringClass.name)
}

Finally a test class is also implemented, since Kotlin doesn't have static methods a companion object with @JvmStatic annotation is created on the class, otherwise test won't be executed. This is probably one of the examples where Kotlin's program get bigger if compared to Java equivalent.

@RunWith(Arquillian::class)
class AdmPhraseRepositoryIT {

    @Inject
    private lateinit var admPhraseRepository: AdmPhraseRepository


    companion object ArquillianTester{

        @JvmStatic
        @Deployment
        fun bootstrapTest(): WebArchive {
            val war = createBasePersistenceWar()
                    .addClass(AdmPhraseRepository::class.java)
                    .addAsWebInfResource("test-beans.xml", "beans.xml")
            println(war.toString(true))

            return war
        }
    }

    @Test
    fun testPersistance(){
        val phrase = AdmPhrase( author = "Torvalds", phrase = "Fck you Nvidia")
        admPhraseRepository.create(phrase)
        assertNotNull(phrase.phraseId)
    }
}


Testing the Application With IntelliJ IDEA and Payara 5

If the application is executed/debugged on IntelliJ IDEA, all should work as expected. In all honesty, I wasn't expecting an easy road, but this worked really well. For instance, a debugging session is initiated with Payara 5:

Debugging as expected

I could also retrieve the results from RDMBS:

Json Phrases

And my Hello World with MicroProfile works, too:

Hello Microprofile

Testing the Application on Oracle Cloud

I created and deployed the same application just to check if there are any other caveats.

Packing applications in Payara Micro is very easy. Basically, you copy your application to a predefined location:

FROM payara/micro
COPY target/integrum-ee.war $DEPLOY_DIR


A scalable Docker Compose descriptor is needed to provide a simple load balancer and RDBMS. This step is also applicable to Kubernetes, Docker Swarm, Rancher, etc.

version: '3.1'
services:
  db:
    image: postgres:9.6.1
    restart: always
    environment:
      POSTGRES_PASSWORD: informatica
      POSTGRES_DB: integrum
    networks:
      - webnet
  web:
    image: "integrum-ee:latest"
    restart: always
    environment:
      JDBC_URL: 'jdbc:postgresql://db:5432/integrum'
      JAVA_TOOL_OPTIONS: '-Xmx64m'
      POSTGRES_PASSWORD: informatica
      POSTGRES_DB: integrum
    ports:
      - 8080
    networks:
      - webnet
  nginx:
    image: nginx:latest
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - web
    ports:
      - "4000:4000"
    networks:
      - webnet
networks:
  webnet:


A simple nginx.conf file is created just to balance access to Payara (eventual and scalable) Workers:

user  nginx;

events {
    worker_connections   1000;
}
http {
        server {
              listen 4000;
              location / {
                proxy_pass http://web:8080;
              }
        }
}


The JTA resource is created via glassfish-resources.xml, expressing RDBMS credentials with env variables:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE resources PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Resource Definitions//EN" "http://glassfish.org/dtds/glassfish-resources_1_5.dtd">
<resources>
    <jdbc-connection-pool name="postgres_appPool" allow-non-component-callers="false" associate-with-thread="false" connection-creation-retry-attempts="0" connection-creation-retry-interval-in-seconds="10" connection-leak-reclaim="false" connection-leak-timeout-in-seconds="0" connection-validation-method="table" datasource-classname="org.postgresql.ds.PGSimpleDataSource" fail-all-connections="false" idle-timeout-in-seconds="300" is-connection-validation-required="false" is-isolation-level-guaranteed="true" lazy-connection-association="false" lazy-connection-enlistment="false" match-connections="false" max-connection-usage-count="0" max-pool-size="200" max-wait-time-in-millis="60000" non-transactional-connections="false" ping="false" pool-resize-quantity="2" pooling="true" res-type="javax.sql.DataSource" statement-cache-size="0" statement-leak-reclaim="false" statement-leak-timeout-in-seconds="0" statement-timeout-in-seconds="-1" steady-pool-size="8" validate-atmost-once-period-in-seconds="0" wrap-jdbc-objects="true">
        <property name="URL" value="${ENV=JDBC_URL}"/>
        <property name="User" value="postgres"/>
        <property name="Password" value="${ENV=POSTGRES_PASSWORD}"/>
        <property name="DatabaseName" value="${ENV=POSTGRES_DB}"/>
        <property name="driverClass" value="org.postgresql.Driver"/>
    </jdbc-connection-pool>
    <jdbc-resource enabled="true" jndi-name="java:app/jdbc/integrumdb" object-type="user" pool-name="postgres_appPool"/>
</resources>


After that, invoking the compose file will bring the application and infrastructure to life. Now, it's time to test it on a real cloud. First, the image is published to Oracle's Container Registry

Oracle Container Registry

Once the container is available at Oracle Cloud, the image becomes usable for any kind of orchestration. For simplicity, I'm directly running the compose file over a bare CentOS VM image (the Wercker+Docker+Kubernetes is an entirely different tutorial).

Oracle Container Registry

To use it on Oracle's infrasctructure, the image ID should be switched to the full name image: iad.ocir.io/tuxtor/microprofile/integrum-ee:1. In the end, the image is pulled and the final result is our Kotlin EE application running over Oracle Cloud.

Oracle Cloud

Testing the Application With NetBeans

JetBrains dropped Kotlin support for NetBeans in 2017. I tried the plugin just for fun on NetBeans 11, but it hangs Netbeans startup while loading Kotlin support, so NetBeans tests were not possible.

Kotlin NetBeans

Testing the Application With Eclipse for Java EE

JetBeans currently develops a Kotlin complement for Eclipse and seems to work fine with pure Kotlin projects; however, the history is very different for Jakarta EE.

After importing the Maven project (previously created in IntelliJ IDEA), many default facets are included in the project. With CDI being the most problematic, it takes a lot of time to build the project in the CDI builder step.

CDI slow

Besides that, Jakarta EE complements/parsers work over the code and not the class files. Hence, the "advanced" menus don't show content, a very cosmetic detail anyway. If Kotlin sources are located under src/main/kotlin, as suggested by JetBrains Maven guide, these are ignored by default. Hence, I took the easy road and moved all the code to src/main/java.

Eclipse facets

Kotlin syntax highlighting works fine, as expected from any language on Eclipse.

Kotlin syntax

However, if you try to deploy the application, it simply doesn't work because the Eclipse compiler does not produce the class file for Kotlin source files — a bug was raised in 2017 and many other users reported issues with Tomcat and Wildfly. Basically, Eclipse WTP is not compatible with Kotlin and deployment/debugging Kotlin code won't work over an application server.

Final Considerations

In the end, I was a little disappointed: Kotlin has great potential for Jakarta EE but it only works if you use IntelliJ IDEA. I'm not sure about IntelliJ CE, but as stated on the JetBrains website, EE support is only included on Ultimate Edition. Maybe this could be changed with more community involvement, but I'm not sure if this is the right direction considering Project Amber.

I would love to hear your feedback. Please drop a note in the comments below.

Kotlin (programming language) mobile app intellij

Published at DZone with permission of Víctor Orozco. See the original article here.

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Mr. Over, the Engineer [Comic]
  • How Observability Is Redefining Developer Roles
  • Top Three Docker Alternatives To Consider
  • AWS Cloud Migration: Best Practices and Pitfalls to Avoid

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: