Categoría: Planet

Microservicios con MicroProfile y Kotlin

Este es un Raw recording de la presentación dictada en Google I/O Extended Guatemala 2019, donde exploramos el uso y motivaciones de utilizar Kotlin en conjunto con MicroProfile, asi como algunos comentarios generales de construcciones utiles de Kotlin…

Etiquetado con: , , , , ,

How to install Payara 5 with NGINX and Let’s Encrypt over Oracle Linux 7.x

Payara SSL

From field experiences I must affirm that one of the greatest and stable combinations is Java Application Servers + Reverse Proxies, although some of the functionality is a clear overlap, I tend to put reverse proxies in front of application servers for the following reasons (please see NGINX page for more details):

  • Load balancing: The reverse proxy acts as traffic cop and could be used as API gateway for clustered instances/backing services
  • Web acceleration: Most of our applications nowadays use SPA frameworks, hence it is worth to cache all the js/css/html files and free the application server from this responsibility
  • Security: Most of the HTTP requests could be intercepted by the reverse proxy before any attempt against the application server, increasing the opportunity to define rules
  • SSL Management: It is easier to install/manage/deploy OpenSSL certificates in Apache/NGINX if compared to Java KeyStores. Besides this, Let’s Encrypt officially support NGINX with plugins.

Requirements

To demonstrate this functionality, this tutorial combines the following stack in a classic (non-docker) way, however most of the concepts could be useful for Docker deployments:

  • Payara 5 as application server
  • NGINX as reverse proxy
  • Let’s encrypt SSL certificates

It is assumed that a clean Oracle Linux 7.x (7.6) box will be used during this tutorial and tests will be executed over Oracle Cloud with root user.

Oracle Linux

Preparing the OS

Since Oracle Linux is binary compatible with RHEL, EPEL repository will be added to get access to Let’s Encrypt. It is also useful to update the OS as a previous step:

yum -y update
yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm

Setting up Payara 5

In order to install Payara application server a couple of dependencies will be needed, specially a Java Developer Kit. For instance OpenJDK is included at Oracle Linux repositories.

yum -y install java-1.8.0-openjdk-headless
yum -y install wget
yum -y install unzip

Once all dependencies are installed, it is time to download, unzip and install Payara. It will be located at /opt following standard Linux conventions for external packages:

cd /opt
wget -O payara-5.191.zip https://search.maven.org/remotecontent?filepath=fish/payara/distributions/payara/5.191/payara-5.191.zip
unzip payara-5.191.zip
rm payara-5.191.zip

It is also useful to create a payara user for administrative purposes, to administrate the domain(s) or to run Payara as Linux service with systemd:

adduser payara
chown -R payara:payara payara5
echo 'export PATH=$PATH:/opt/payara5/glassfish/bin' >> /home/payara/.bashrc
chown payara:payara /home/payara/.bashrc

A systemd unit is also needed:

echo '[Unit]
Description = Payara Server v5
After = syslog.target network.target

[Service]
User=payara
ExecStart = /usr/bin/java -jar /opt/payara5/glassfish/lib/client/appserver-cli.jar start-domain
ExecStop = /usr/bin/java -jar /opt/payara5/glassfish/lib/client/appserver-cli.jar stop-domain
ExecReload = /usr/bin/java -jar /opt/payara5/glassfish/lib/client/appserver-cli.jar restart-domain
Type = forking

[Install]
WantedBy = multi-user.target' > /etc/systemd/system/payara.service
systemctl enable payara

Additionally if remote administration is needed, secure admin should be enabled:

sudo -u payara /opt/payara5/bin/asadmin --host localhost --port 4848 change-admin-password
systemctl start payara
sudo -u payara /opt/payara5/bin/asadmin --host localhost --port 4848 enable-secure-admin
systemctl restart payara

Payara Boot

Oracle Cloud default configuration will create a VNIC attached to your instance, hence you should check the rules in order to allow access to ports.

Ingres Rules

By default, Oracle Linux instances have a restricted set of rules in iptables and SELinux, hence ports should be opened with firewalld and SELinux should be configured to allow reverse proxy traffic:

firewall-cmd --zone=public --permanent --add-service=http
firewall-cmd --zone=public --permanent --add-service=https
firewall-cmd --zone=public --permanent --add-port=4848/tcp
setsebool -P httpd_can_network_connect 1

With this, the access is guaranteed to http+https+payara admin port.

Setting up NGINX reverse proxy

NGINX is available at EPEL:

yum -y install nginx
systemctl enable nginx

At this time your will need a FQDN pointing to your server, otherwhise Let’s encrypt validation won’t work. For this tutorial the ocl.nabenik.com domain will be used. If your domain propagated properly you should see a page like this:

NGINX Proxy

Don’t worry the Fedora logo is due EPEL usage, but you’re running Oracle Linux :).

Now it’s time to setup NGINX as reverse proxy, an opinionated deployment option is to create a /etc/nginx/sites-available and /etc/nginx/sites-enabled structure inside NGINX configuration, to isolate/manage multiple domains with the same instance (aka virtual hosts).

mkdir -p /etc/nginx/sites-available
mkdir -p /etc/nginx/sites-enabled
mkdir -p /var/www/ocl.nabenik.com/
chown -R nginx:nginx /var/www/ocl.nabenik.com

echo 'server {
    server_name ocl.nabenik.com;

    gzip on;
    gzip_types      text/css text/javascript text/plain application/xml;
    gzip_min_length 1000;

    location ^~ /.well-known/acme-challenge/ {
        allow all;
        root /var/www/ocl.nabenik.com/;
        default_type "text/plain";
        try_files $uri =404;
    }

    location / {
        proxy_pass             http://localhost:8080;
        proxy_connect_timeout       300;
        proxy_send_timeout          300;
        proxy_read_timeout          300;
        send_timeout                300;
    }

    error_page  500 502 503 504  /50x.html;
    location = /50x.html {
        root  /usr/share/nginx/html;
    }

    listen 80;
}' > /etc/nginx/sites-available/ocl.nabenik.com.conf

To enable the new host, a symlink is created on sites-enabled:

ln -s /etc/nginx/sites-available/ocl.nabenik.com.conf /etc/nginx/sites-enabled/ocl.nabenik.com.conf

After that you should include the following line inside /etc/nginx/nginx.conf, just before config file ending.

include /etc/nginx/sites-enabled/*.conf;

It is also useful to check your configuration with nginx -t, if all works property you should reach payara after NGINX reload.

Reverse Payara

Setting up Let’s Encrypt

Once the reverse proxy is working, certbot should be enough to add an SSL certificate, the plugin itself will create a challenge at ^~ /.well-known/acme-challenge/, hence the proxy exclusion is mandatory (as reflected in the previous configuration step).

yum install -y certbot-nginx
certbot --nginx -d ocl.nabenik.com

One of the caveats of using certbot is the dependency of python version. Another alternative if you find any issues is to install it with pip

yum install -y python-pip
pip install certbot-nginx
certbot --nginx -d ocl.nabenik.com

If everything works as expected, you should see the Payara page under SSL.

Payara SSL

Finally and most importantly, Let’s Encrypt certificates are valid just for 90 days, hence you could add certification renewal (crontab -e) as a cron task

15 3 * * * /usr/bin/certbot renew --quiet

Hands on Lab: Jakarta EE 8, MicroProfile y Oracle Cloud

Recientemente tuve la oportunidad de dictar dos conferencias en Es-Conference, uno de los mayores eventos JVM en México (y uno de mis favoritos en el circuito JVM en general). Una de ellas fue un Live Coding/Hands on lab acerca de Java EE 8 y Eclipse M…

Etiquetado con: , , , , , ,

Getting started with Java EE 8, Java 11, Eclipse for Java Enterprise and Wildfly 16

In this mini-tutorial we will demonstrate the configuration of a pristine development environment with Eclipse, JBoss Tools and Wildfly Application Server on MacOS.From JBoss with loveIf you have been in the Java EE space for a couple of years, Eclip…

The state of Kotlin for Jakarta EE/MicroProfile traditional applications

Kotlin EE

This easter 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 works as language and plays well with Maven, I’m able to use many of the EE API for “services”, however the roadblock is not the language or libraries, is the tooling support.

The experience is superb on IntelliJ IDEA and all works as expected, however the IDE is a barrier 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 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 Javas. I do a lot of development in different languages including Java for backends, Kotlin for mobile, JavaScript on front-ends, Bash for almost every automation task so I knew the streghts of Kotlin from the mobile space where is specially important, basically most of the Java development on Android is currently a Java 7+Lambdas experience.

My top three 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. Interesting Kotlin features are highlighted.

Source code is available at GitHub repo https://github.com/tuxtor/integrum-ee.

Project configuration

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

Besides adding Kotlin dependencies to a regular EE pom.xmla special configuration is needed for the all-open plugin, Jakarta EE works with proxy entities that inherit from your original code. To make it simple all CDI, EJB and JAX-RS important 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 need to provide also a repository, the repository is a classic CRUD component injectable with CDI, 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 querys 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 by 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, specially 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.

Is also observed the elvis operator in action (in DELETE), if the entity is not found the alternative return is fired immediately, 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 that this 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 the data source it will be null and 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 gets 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 = "Intelligence is the ability to avoid doing work, yet getting the work done")
        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 works as expected, sincerely 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

As Oracle Groundbreaker Ambassador I have access to instances on Oracle Cloud, hence 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 with 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 the 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+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 become usable for any kind of orchestation, for simplicity I’m running directly 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, being CDI 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 Eclipse compiler does not produce the class file for Kotlin source files, a bug was raised in 2017 and many other users report 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

At the end I was a little dissapointed, Kotlin has great potential for Jakarta EE but it works only if you use IntelliJ IDEA. I’m not sure about IntelliJ CE but as stated in JetBrains website, EE support is only included on Ultimate Edition, maybe this could be changed with more community involvemente but I’m not sure if this is the right direction considering project Amber.

Top