Saturday, May 28, 2022

The lost art of learning ... (a new programming language)

I remember the time, 20 years ago, when as a junior software developer I was driven by constant, non-stoppable desire to learn new things: new features, techniques, frameworks, architectures, styles, practices, ... and surely, new programming languages. This curiosity, hunger to learn, drove me to consume as many books as possible and to hunt around the clock for the scanty online publications available at that time.

Year after year, the accumulated knowledge backed by invaluable experience led me to settle on proven and battle-tested (in production) choices, which have worked, work these days and I am sure will work in the future. For me, the foundation was set in Java (later Scala as well) and JVM platform in general. This is where my expertise lays in, but don't get me wrong, I still try to read a lot, balancing on the edge between books and enormous amount of the blog posts, articles, talks and podcast. How many programming languages I was able to master for the last, let say, 10 years? If we count the JVM ecosystem only, Scala and Groovy would make the list, but otherwise - none, zero, 0. The pace of learning on this front has slowed for me, significantly ...

Why is that? Did I get burned out? Nope (thanks God). Fed up by development? Nope, hopefully never. Opionated? Yes, certainly. Older? Definitely. Less curious? Not really. To be fair, there are so many things going on in the JVM ecosystem, so many projects are being developed by the community, it has become increasingly difficult to find the time for something else! And frankly, why bother if you could do Java for the next 20 years without any fear of it disappearing or becoming irrelevant.

JVM is amazing platform but its fundamental principles heavily impact certain design aspects of any programming language built on top of it. It is good and bad (everything is a trade-off), but with the time, you as a developer get used to it, questioning if anything else makes sense. As many others, I ended up in the bubble which has to be blown up. How? I decided to learn Rust, and go way beyond the basics.

But no worries, I do not intend to sell you Rust but rather share an alarming discovery: the lost art of learning a new programming language. It all started as usual, crunching through the blogs, people's and companies' experiences and finally getting the book, Programming Rust: Fast, Safe Systems Development (really good one but the official The Rust Programming Language is equally awesome). A few months (!) later, I was done reading, feeling ready for delivering cool, production-ready Rust applications, or so I thought ... The truth - I was far, very far from it ... and those are the things I did wrong.

  • Rust is concise (at least, to my taste), modern and powerful, but it is not a simple language. If some language aspects are not clear, do not skip over but try to understand them. If book is not doing sufficiently good job at it, look around till you get them, otherwise you risk to miss even more. I did not do that (overly confident that nothing could be harder than learning functional Scala) and at some point I was somewhat lost.

  • In the same vein, Rust has unique perspective on many things, including concurrency and memory management, and investing the time to understand what those are is a must (the Java/JVM background is not very helpful here). Essentially, you have to "unlearn" in order to grasp certain new concepts, this is not easy and confuses the brain.

  • Stay very focused and methodical, try to read at least few pages **every day**. Yes, we are all super busy, always distracted, have families and started to travel again, ... yada yada yada. Also, Programming Rust: Fast, Safe Systems Development is not a slim book: ~800 pages! Irregular, long breaks in reading were killing my progress.

  • Practice, practice and practice. If you have some pet projects in mind, this is great, if not - just come up with anything ad-hoc but try to use the things you have just read about. Learning the "theory" is great, but mastering the programming language is all about using it every day to solve problems. I screwed there epically by following "bed time story" style ...

  • Learn the coding practices and patterns by looking into the language's ecosystem, in this regard a curated list of Rust code and resources is super useful.

  • Subscribe to newsletters to stay up to date and be aware what is going on in the core development and in the community. In case of Rust, the This Week in Rust turned out to be invaluable.

  • Watch talks and presentations, luckily most of the conferences do publish all their videos online these days. I stumbled upon the official Rust YouTube channel and since then follow it closely.

Learning new programming language is rewarding experience, in particular when it happens to be Rust, a most beloved programming language for the last six years. But not only because of that, Rust is perfect fit for a system programming, something Java/JVM is inherently not suited very well (yes, it is better now with JEP 330: Launch Single-File Source-Code Programs, jbang and GraalVM native images but still far from being perfect for a task). From the other side, it is quite challenging when the programming language you are learning is not part of your day-to-day job.

As of this moment, I am not sure if Rust ever becomes my primary programming language but one thing I do know: I think every passionate software developer should strive to learn at least one new programming language every 1-2 years (we have so many good ones). It is amazing mental exercice and I used to follow this advice back in the days but lost the sight of it along the journey. I really should have not.

Happy learning and peace to everyone!

Friday, March 25, 2022

The show must go on: JDK-18 is here!

It feels like JDK-17 release just landed not so long ago, but there is a new one already out: please welcome JDK-18! Although it is not an LTS release, there are quite a few interesting features to talk about (the preview and incubator ones are not included).

The JDK-18 comes in with a large number of the security enhancements, including further work on phasing out SecurityManager and its APIs:

And while we are enjoying the perils of JDK-18, the early access builds of JDK-19 are already available. There are not much details of what is coming just yet, but may be Project Loom would finally make its appearance in some form or another? Who knows ...

Peace to everyone!

Thursday, January 20, 2022

So you want to expose your JAX-RS services over HTTP/2 ...

Nonetheless HTTP/2 is about six years old (already!), and HTTP/3 is around the corner, it looks like the majority of the web applications and systems are stuck in time, operating over HTTP/1.x protocol. And we are not even talking about legacy systems, it is not difficult to stumble upon greenfield web applications that ignore the existence of the HTTP/2 in principle. A few years ago the excuses like "immature HTTP/2 support by container of my choice" might have been justified but these days all the major web containers (Jetty, Apache Tomcat, Netty, Undertow) offer a first class HTTP/2 support, so why not use it?

The today's post is all about exposing and consuming your JAX-RS services over HTTP/2 protocol using latest 3.5.0 release of the Apache CXF framework, a compliant JAX-RS 2.1 implementation. Although HTTP/2 does not require encryption, it is absolutely necessary these days for deploying real-world production systems. With that being said, we are going to cover both options: h2c (HTTP/2 over clear text, useful for development) and regular h2 (HTTP/2 over TLS).

Our JAX-RS resource, PeopleResource, exposes only one @GET endpoint with hardcoded response specification (to keeps things simple here):

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import com.example.model.Person;

import reactor.core.publisher.Flux;

@Path("/people")
public class PeopleResource {
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Flux<Person> getPeople() {
        return Flux.just(new Person("a@b.com", "Tom", "Knocker"));
    }
}

The usage of reactive types (Project Reactor in this case) is intentional here since this is most likely what you are going to end up with (but to be fair, not a requirement).

<dependency>
	<groupId>io.projectreactor</groupId>
	<artifactId>reactor-core</artifactId>
	<version>3.4.14</version>
</dependency>

<dependency>
	<groupId>org.apache.cxf</groupId>
	<artifactId>cxf-rt-rs-extension-reactor</artifactId>
	<version>3.5.0</version>
</dependency>

To be noted, other options like RxJava3 / RxJava2 are also available out of the box. The Person model is as straightforward as it gets:

public class Person {
    private String email;
    private String firstName;
    private String lastName;
    
    // Getters and setters here
}

To benefit from HTTP/2 support, you need to pick your web server/container (Jetty, Netty, or Undertow) and (optionally) include a couple of additional dependencies (which might be specific to server/container and/or JDK version you are using). The official documentation has it covered in great details, for demonstration purposes we are going to use Jetty (9.4.44.v20210927) and run on JDK-17, the latest LTS version of the OpenJDK.

<dependency>
	<groupId>org.apache.cxf</groupId>
	<artifactId>cxf-rt-transports-http-jetty</artifactId>
	<version>3.5.0</version>
</dependency>

<dependency>
	<groupId>org.eclipse.jetty.http2</groupId>
	<artifactId>http2-server</artifactId>
	<version>9.4.44.v20210927</version>
</dependency>

<dependency>
	<groupId>org.eclipse.jetty</groupId>
	<artifactId>jetty-alpn-server</artifactId>
	<version>9.4.44.v20210927</version>
</dependency>

<dependency>
	<groupId>org.eclipse.jetty</groupId>
	<artifactId>jetty-alpn-java-server</artifactId>
	<version>9.4.44.v20210927</version>
</dependency>

Apache CXF lets you package and run your services as standalone executable JARs (or GraalVM's native images in certain cases), no additional frameworks required besides the main class.

import org.apache.cxf.Bus;
import org.apache.cxf.BusFactory;
import org.apache.cxf.endpoint.Server;
import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
import org.apache.cxf.jaxrs.lifecycle.SingletonResourceProvider;
import org.apache.cxf.jaxrs.utils.JAXRSServerFactoryCustomizationUtils;
import org.apache.cxf.transport.http.HttpServerEngineSupport;

import com.example.rest.PeopleResource;
import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;

public class ServerStarter {
    public static void main( final String[] args ) throws Exception {
        final Bus bus = BusFactory.getDefaultBus();
        bus.setProperty(HttpServerEngineSupport.ENABLE_HTTP2, true);

        final JAXRSServerFactoryBean bean = new JAXRSServerFactoryBean();
        bean.setResourceClasses(PeopleResource.class);
        bean.setResourceProvider(PeopleResource.class, 
            new SingletonResourceProvider(new PeopleResource()));
        bean.setAddress("http://localhost:19091/services");
        bean.setProvider(new JacksonJsonProvider());
        bean.setBus(bus);
        
        JAXRSServerFactoryCustomizationUtils.customize(bean);
        Server server = bean.create();
        server.start();
    }
}

The key configuration here is HttpServerEngineSupport.ENABLE_HTTP2 property which has to be set to true in order to notify the transport provider of your choice to turn HTTP/2 support on. Without TLS configuration your JAX-RS resources become accessible over h2c (HTTP/2 over clear text), additionally to HTTP/1.1. Let us give it a try right away (please make sure your have JDK-17 available by default).

$ mvn clean package
$ java -jar target/jaxrs-standalone-jetty-http2-0.0.1-SNAPSHOT-h2c.jar

[INFO] 2022-01-16 11:11:16.255 org.apache.cxf.endpoint.ServerImpl -[] Setting the server's publish address to be http://localhost:19091/services
[INFO] 2022-01-16 11:11:16.322 org.eclipse.jetty.util.log -[] Logging initialized @482ms to org.eclipse.jetty.util.log.Slf4jLog
[INFO] 2022-01-16 11:11:16.361 org.eclipse.jetty.server.Server -[] jetty-9.4.44.v20210927; built: 2021-09-27T23:02:44.612Z; git: 8da83308eeca865e495e53ef315a249d63ba9332; jvm 17+35-2724
[INFO] 2022-01-16 11:11:16.449 o.e.jetty.server.AbstractConnector -[] Started ServerConnector@3f4faf53{HTTP/1.1, (h2c, http/1.1)}{localhost:19091}
[INFO] 2022-01-16 11:11:16.449 org.eclipse.jetty.server.Server -[] Started @613ms
[WARN] 2022-01-16 11:11:16.451 o.e.j.server.handler.ContextHandler -[] Empty contextPath
[INFO] 2022-01-16 11:11:16.466 o.e.j.server.handler.ContextHandler -[] Started o.e.j.s.h.ContextHandler@495ee280{/,null,AVAILABLE}
...

It is as simple as that, if you don't believe it, Jetty dumps quite handy message in the console regarding the supported protocols: {HTTP/1.1, (h2c, http/1.1)}. The Swiss army knife of the web developer, curl, is the easiest way to verify things are working as expected.

$ curl http://localhost:19091/services/people --http2 -iv                                                                                                 
...
* Connected to localhost (127.0.0.1) port 19091 (#0)                                                                                                      
> GET /services/people HTTP/1.1
> Host: localhost:19091
> User-Agent: curl/7.71.1
> Accept: */*
> Connection: Upgrade, HTTP2-Settings
> Upgrade: h2c
> HTTP2-Settings: AAMAAABkAAQCAAAAAAIAAAAA
...
* Mark bundle as not supporting multiuse                                                                                                                  
< HTTP/1.1 101 Switching Protocols 
* Received 101                                                                                                                                            
* Using HTTP2, server supports multi-use                                                                                                                  
* Connection state changed (HTTP/2 confirmed)                                                                                                             
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0                                                                          
* Connection state changed (MAX_CONCURRENT_STREAMS == 128)!                                                                                               
< HTTP/2 200                                                                                                                                             
< server: Jetty(9.4.44.v20210927)                                                                                                                       
< date: Sun, 16 Jan 2022 17:08:08 GMT                                                                                                                   
< content-type: application/json
< content-length: 60
...
HTTP/1.1 101 Switching Protocols
HTTP/2 200
server: Jetty(9.4.44.v20210927)                                                                                                                           
date: Sun, 16 Jan 2022 17:08:08 GMT                                                                                                                       
content-type: application/json                                                                                                                            
content-length: 60                                                                                                                                        
                                                                                                                                                          
[{"email":"a@b.com","firstName":"Tom","lastName":"Knocker"}]                                                                                              

There is something interesting happening here. Nonetheless we have asked for HTTP/2, the client connects over HTTP/1.1 first and only than switches the protocol (HTTP/1.1 101 Switching Protocols) to HTTP/2. This is expected for HTTP/2 over clear text (h2c), however we could use HTTP/2 prior knowledge to skip over the protocol upgrade steps.

$ curl http://localhost:19091/services/people --http2-prior-knowledge -iv                                                                 
...
* Connected to localhost (127.0.0.1) port 19091 (#0)                                                                                      
* Using HTTP2, server supports multi-use                                                                                                  
* Connection state changed (HTTP/2 confirmed)                                                                                             
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0                                                          
* Using Stream ID: 1 (easy handle 0x274df30)                                                                                              
> GET /services/people HTTP/2                                                                                                             
> Host: localhost:19091                                                                                                                   
> user-agent: curl/7.71.1                                                                                                                 
> accept: */*                                                                                                                             
>                                                                                                                                         
* Connection state changed (MAX_CONCURRENT_STREAMS == 128)!                                                                               
< HTTP/2 200                                                                                                                              
< server: Jetty(9.4.44.v20210927)                                                                                                         
< date: Sun, 16 Jan 2022 17:06:40 GMT                                                                                                     
< content-type: application/json                                                                                                          
< content-length: 60                                                                                                                    
...
HTTP/2 200                                                  
server: Jetty(9.4.44.v20210927)                                                                                                           
date: Sun, 16 Jan 2022 17:06:40 GMT                                                                                                       
content-type: application/json                                                                                                            
content-length: 60                                                                                                                        
                                                                                                                                          
[{"email":"a@b.com","firstName":"Tom","lastName":"Knocker"}]                                                                              

Configuring HTTP/2 over TLS requires just a bit more efforts to setup the certificates and key managers (we are using self-signed certificates issued to localhost, please check Creating sample HTTPS server for fun and profit if you are curious how to generate your own):

import org.apache.cxf.Bus;
import org.apache.cxf.BusFactory;
import org.apache.cxf.configuration.jsse.TLSParameterJaxBUtils;
import org.apache.cxf.configuration.jsse.TLSServerParameters;
import org.apache.cxf.configuration.security.KeyManagersType;
import org.apache.cxf.configuration.security.KeyStoreType;
import org.apache.cxf.endpoint.Server;
import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
import org.apache.cxf.jaxrs.lifecycle.SingletonResourceProvider;
import org.apache.cxf.jaxrs.utils.JAXRSServerFactoryCustomizationUtils;
import org.apache.cxf.transport.http.HttpServerEngineSupport;
import org.apache.cxf.transport.http_jetty.JettyHTTPServerEngineFactory;

import com.example.rest.PeopleResource;
import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;

public class TlsServerStarter {
    public static void main( final String[] args ) throws Exception {
        final Bus bus = BusFactory.getDefaultBus();
        bus.setProperty(HttpServerEngineSupport.ENABLE_HTTP2, true);
        
        final KeyStoreType keystore = new KeyStoreType();
        keystore.setType("JKS");
        keystore.setPassword("strong-passw0rd-here");
        keystore.setResource("certs/server.jks");
        
        final KeyManagersType kmt = new KeyManagersType();
        kmt.setKeyStore(keystore);
        kmt.setKeyPassword("strong-passw0rd-here");
        
        final TLSServerParameters parameters = new TLSServerParameters();
        parameters.setKeyManagers(TLSParameterJaxBUtils.getKeyManagers(kmt));
        final JettyHTTPServerEngineFactory factory = new JettyHTTPServerEngineFactory(bus);
        factory.setTLSServerParametersForPort("localhost", 19091, parameters);
        
        final JAXRSServerFactoryBean bean = new JAXRSServerFactoryBean();
        bean.setResourceClasses(PeopleResource.class);
        bean.setResourceProvider(PeopleResource.class, 
            new SingletonResourceProvider(new PeopleResource()));
        bean.setAddress("https://localhost:19091/services");
        bean.setProvider(new JacksonJsonProvider());
        bean.setBus(bus);
        
        JAXRSServerFactoryCustomizationUtils.customize(bean);
        Server server = bean.create();
        server.start();
    }
}

Now if we repeat the experiment, the results are going to be quite different.

$ mvn clean package
$ java -jar target/jaxrs-standalone-jetty-http2-0.0.1-SNAPSHOT-h2.jar

[INFO] 2022-01-17 19:06:37.481 org.apache.cxf.endpoint.ServerImpl -[] Setting the server's publish address to be https://localhost:19091/services
[INFO] 2022-01-17 19:06:37.536 org.eclipse.jetty.util.log -[] Logging initialized @724ms to org.eclipse.jetty.util.log.Slf4jLog
[INFO] 2022-01-17 19:06:37.576 org.eclipse.jetty.server.Server -[] jetty-9.4.44.v20210927; built: 2021-09-27T23:02:44.612Z; git: 8da83308eeca865e495e53ef315a249d63ba9332; jvm 17+35-2724
[INFO] 2022-01-17 19:06:37.749 o.e.jetty.server.AbstractConnector -[] Started ServerConnector@163370c2{ssl, (ssl, alpn, h2, http/1.1)}{localhost:19091}
[INFO] 2022-01-17 19:06:37.749 org.eclipse.jetty.server.Server -[] Started @937ms
[WARN] 2022-01-17 19:06:37.752 o.e.j.server.handler.ContextHandler -[] Empty contextPath
[INFO] 2022-01-17 19:06:37.772 o.e.j.server.handler.ContextHandler -[] Started o.e.j.s.h.ContextHandler@403f0a22{/,null,AVAILABLE}
...

The list of the supported protocols listed by Jetty includes few newcomers: {ssl, (ssl, alpn, h2, http/1.1)}. The presence of ALPN (Application-Layer Protocol Negotiation) is very important as it allows the application layer to negotiate which protocol should be selected over a TLS connection. Without further ado, let us see that in action.

$ curl https://localhost:19091/services/people --http2 -k 
  
* Connected to localhost (127.0.0.1) port 19091 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
...
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN, server accepted to use h2
* Server certificate:
*  subject: C=XX; ST=XX; L=XX; O=XX; CN=localhost
*  start date: Jan 18 00:16:42 2022 GMT
*  expire date: Nov  7 00:16:42 2024 GMT
*  issuer: C=XX; ST=XX; L=XX; O=XX; CN=localhost
*  SSL certificate verify result: self signed certificate (18), continuing anyway.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
...
> GET /services/people HTTP/2
> Host: localhost:19091
> user-agent: curl/7.71.1
> accept: */*
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* Connection state changed (MAX_CONCURRENT_STREAMS == 128)!
< HTTP/2 200
< server: Jetty(9.4.44.v20210927)
< date: Tue, 18 Jan 2022 00:19:20 GMT
< content-type: application/json
< content-length: 60

HTTP/2 200
server: Jetty(9.4.44.v20210927)
date: Tue, 18 Jan 2022 00:19:20 GMT
content-type: application/json
content-length: 60

[{"email":"a@b.com","firstName":"Tom","lastName":"Knocker"}]

As we can see, the client and server negotiated the protocols from the start and HTTP/2 has been picked, completely bypassing the HTTP/1.1 101 Switching Protocols dance we have seen before.

Hopefully things are looking exciting already, but to be fair, it is very likely that your are already hosting JAX-RS services inside applications powered by widely popular Spring Boot framework. Wouldn't it be awesome to have HTTP/2 support right there? Absolutely, and in fact you don't need anything special from the Apache CXF besides using the provided Spring Boot starters.

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter</artifactId>
	<version>2.6.2</version>
</dependency>

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-jetty</artifactId>
	<version>2.6.2</version>
</dependency>

<dependency>
	<groupId>org.apache.cxf</groupId>
	<artifactId>cxf-spring-boot-starter-jaxrs</artifactId>
	<version>3.5.0</version>
	<exclusions>
		<exclusion>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-tomcat</artifactId>
		</exclusion>
	</exclusions>
</dependency>

The application configuration is minimal but still required (although in future it should be completely auto-configurable):

import org.apache.cxf.Bus;
import org.apache.cxf.endpoint.Server;
import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
import org.apache.cxf.jaxrs.utils.JAXRSServerFactoryCustomizationUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.example.rest.PeopleResource;
import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;

@Configuration
public class AppConfig {
    @Bean
    public Server server(Bus bus, PeopleResource service) {
        JAXRSServerFactoryBean bean = new JAXRSServerFactoryBean();
        bean.setBus(bus);
        bean.setServiceBean(service);
        bean.setProvider(new JacksonJsonProvider());
        bean.setAddress("/");
        JAXRSServerFactoryCustomizationUtils.customize(bean);
        return bean.create();
    }
}

Everything else, including TLS configuration, is done through configuration properties, which are usually provided inside application.yml (or externalized altogether):

server:
  port: 19091
  http2:
    enabled: true
---
spring:
  config:
    activate:
      on-profile: h2
server:
  ssl:
    key-store: "classpath:certs/server.jks"
    key-store-password: "strong-passw0rd-here"
    key-password: "strong-passw0rd-here"

The HTTP/2 protocol is enabled by setting server.http2.enabled configuration property to true, the Apache CXF is not involved in any way, it is solely offered by Spring Boot. The TLS/SSL is activated by Spring profile h2, otherwise it runs HTTP/2 over clear text.

$ java -jar  target/jaxrs-spring-boot-jetty-http2-0.0.1-SNAPSHOT.jar
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.6.2)

[INFO] 2022-01-19 20:08:55.645 o.h.validator.internal.util.Version -[] HV000001: Hibernate Validator 6.2.0.Final
[INFO] 2022-01-19 20:08:55.646 com.example.ServerStarter -[] No active profile set, falling back to default profiles: default
[INFO] 2022-01-19 20:08:56.777 org.eclipse.jetty.util.log -[] Logging initialized @2319ms to org.eclipse.jetty.util.log.Slf4jLog
[INFO] 2022-01-19 20:08:57.008 o.s.b.w.e.j.JettyServletWebServerFactory -[] Server initialized with port: 19091
[INFO] 2022-01-19 20:08:57.011 org.eclipse.jetty.server.Server -[] jetty-9.4.44.v20210927; built: 2021-09-27T23:02:44.612Z; git: 8da83308eeca865e495e53ef315a249d63ba9332; jvm 17+35-2724
[INFO] 2022-01-19 20:08:57.052 o.e.j.s.h.ContextHandler.application -[] Initializing Spring embedded WebApplicationContext
[INFO] 2022-01-19 20:08:57.052 o.s.b.w.s.c.ServletWebServerApplicationContext -[] Root WebApplicationContext: initialization completed in 1352 ms
[INFO] 2022-01-19 20:08:57.237 org.eclipse.jetty.server.session -[] DefaultSessionIdManager workerName=node0
[INFO] 2022-01-19 20:08:57.238 org.eclipse.jetty.server.session -[] No SessionScavenger set, using defaults
[INFO] 2022-01-19 20:08:57.238 org.eclipse.jetty.server.session -[] node0 Scavenging every 660000ms
[INFO] 2022-01-19 20:08:57.245 org.eclipse.jetty.server.Server -[] Started @2788ms
[INFO] 2022-01-19 20:08:57.422 org.apache.cxf.endpoint.ServerImpl -[] Setting the server's publish address to be /
[INFO] 2022-01-19 20:08:58.038 o.e.j.s.h.ContextHandler.application -[] Initializing Spring DispatcherServlet 'dispatcherServlet'
[INFO] 2022-01-19 20:08:58.038 o.s.web.servlet.DispatcherServlet -[] Initializing Servlet 'dispatcherServlet'
[INFO] 2022-01-19 20:08:58.038 o.s.web.servlet.DispatcherServlet -[] Completed initialization in 0 ms
[INFO] 2022-01-19 20:08:58.080 o.e.jetty.server.AbstractConnector -[] Started ServerConnector@ee86bcb{HTTP/1.1, (http/1.1, h2c)}{0.0.0.0:19091}
[INFO] 2022-01-19 20:08:58.081 o.s.b.w.e.jetty.JettyWebServer -[] Jetty started on port(s) 19091 (http/1.1, h2c) with context path '/'
[INFO] 2022-01-19 20:08:58.093 com.example.ServerStarter -[] Started ServerStarter in 2.939 seconds (JVM running for 3.636)
...

The already familiar list of protocols appears in the console: {HTTP/1.1, (http/1.1, h2c)}. To activate HTTP/2 over TLS we could pass --spring.profiles.active=h2 command line argument, for example:

$ java -jar  target/jaxrs-spring-boot-jetty-http2-0.0.1-SNAPSHOT.jar --spring.profiles.active=h2                                                                                       
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.6.2)

[INFO] 2022-01-19 20:13:17.999 com.example.ServerStarter -[] The following profiles are active: h2
[INFO] 2022-01-19 20:13:17.999 o.h.validator.internal.util.Version -[] HV000001: Hibernate Validator 6.2.0.Final
[INFO] 2022-01-19 20:13:19.124 org.eclipse.jetty.util.log -[] Logging initialized @2277ms to org.eclipse.jetty.util.log.Slf4jLog
[INFO] 2022-01-19 20:13:19.368 o.s.b.w.e.j.JettyServletWebServerFactory -[] Server initialized with port: 19091
[INFO] 2022-01-19 20:13:19.398 org.eclipse.jetty.server.Server -[] jetty-9.4.44.v20210927; built: 2021-09-27T23:02:44.612Z; git: 8da83308eeca865e495e53ef315a249d63ba9332; jvm 17+35-2724
[INFO] 2022-01-19 20:13:19.433 o.e.j.s.h.ContextHandler.application -[] Initializing Spring embedded WebApplicationContext
[INFO] 2022-01-19 20:13:19.433 o.s.b.w.s.c.ServletWebServerApplicationContext -[] Root WebApplicationContext: initialization completed in 1380 ms
[INFO] 2022-01-19 20:13:19.618 org.eclipse.jetty.server.session -[] DefaultSessionIdManager workerName=node0
[INFO] 2022-01-19 20:13:19.618 org.eclipse.jetty.server.session -[] No SessionScavenger set, using defaults
[INFO] 2022-01-19 20:13:19.619 org.eclipse.jetty.server.session -[] node0 Scavenging every 660000ms                                                         [INFO] 2022-01-19 20:13:19.626 org.eclipse.jetty.server.Server -[] Started @2779ms
[INFO] 2022-01-19 20:13:19.823 org.apache.cxf.endpoint.ServerImpl -[] Setting the server's publish address to be /
[INFO] 2022-01-19 20:13:20.394 o.e.j.s.h.ContextHandler.application -[] Initializing Spring DispatcherServlet 'dispatcherServlet'
[INFO] 2022-01-19 20:13:20.394 o.s.web.servlet.DispatcherServlet -[] Initializing Servlet 'dispatcherServlet'
[INFO] 2022-01-19 20:13:20.395 o.s.web.servlet.DispatcherServlet -[] Completed initialization in 1 ms
[INFO] 2022-01-19 20:13:20.775 o.e.jetty.server.AbstractConnector -[] Started SslValidatingServerConnector@7e3181aa{SSL, (ssl, alpn, h2, http/1.1)}{0.0.0.0:19091}
[INFO] 2022-01-19 20:13:20.776 o.s.b.w.e.jetty.JettyWebServer -[] Jetty started on port(s) 19091 (ssl, alpn, h2, http/1.1) with context path '/'
[INFO] 2022-01-19 20:13:20.786 com.example.ServerStarter -[] Started ServerStarter in 3.285 seconds (JVM running for 3.939)
...

And we see {SSL, (ssl, alpn, h2, http/1.1)} this time around. If you would like to repeat the experiment with the curl commands we executed before, feel free to do so, the observed results should be the same. It is worth mentioning that along with Jetty, Spring Boot bakes first-class support for Apache Tomcat, Netty (Reactor Netty to be precise) and Undertow.

Huh, quite likely you are now convinced that HTTP/2 is pretty well supported these days and it is here for you to take advantage of. We have seen Spring Boot and Apache CXF in action, but Quarkus, Micronaut, Helidon (and many others) are on a par with HTTP/2 support, enjoy!

The complete project sources are available on Github.

Friday, November 26, 2021

The final days of finalizers in Java

If you are developing in Java long enough, you are surely aware of the Object::finalize() method and the concept of the finalizers in general.

protected void finalize() throws Throwable
    
...
Called by the garbage collector on an object when garbage collection determines that there are no more references to the object. A subclass overrides the finalize method to dispose of system resources or to perform other cleanup.
...

This reasonably good idea has gotten quite bad reputation over the years and definitely is one of the worst nightmares of the JVM developers. There are enough details and horror stories on the web related to finalizers (who did not implement it at least once?), but the end is near: JEP-421: Deprecate Finalization for Removal, proposed to become a part of the JDK-18 release, kicks off the process to phase finalizers out.

In this rather short post we are going to talk about java.lang.ref.Cleaner, the mechanism alternative to the finalizers, which allows to perform the cleaning actions once the corresponding object instance becomes phantom reachable. It was introduced by JDK-8138696 and is available starting from JDK-9 and onwards.

The usage of java.lang.ref.Cleaner is pretty straightforward (although advanced usage scenarios are also possible):

To illustrate it, let us consider a quick example. Assume we have been designing a ResourceAccessor class that accesses (or even allocates) some resources (possibly, natives ones, which are outside of the JVM control).

import java.lang.ref.Cleaner;

public class ResourceAccessor implements AutoCloseable {
    private static final Cleaner cleaner = Cleaner.create();
    private final Cleaner.Cleanable cleanable;
    private final Resource resource;

    public ResourceAccessor() {
        this.resource = new Resource();
        this.cleanable = cleaner.register(this, cleaner(resource));
    }

    @Override
    public void close() throws Exception {
        cleanable.clean();
    }

    private static Runnable cleaner(Resource resource) {
        return () -> {
            // Perform cleanup actions
            resource.release();
        };
    }
}

The ResourceAccessor allocates the resource and registers the cleanup action on construction, keeping the reference to Cleaner.Cleanable instance. It also implements AutoCloseable interface and the close() method just delegates to the Cleaner.Cleanable::clean.

Please notice that cleanup action should not hold any references to the object instance registered for cleanup, otherwise that instance will not become phantom reachable and the cleaning action will not be invoked automatically. This is why we have wrapped the lamdba expression behind the cleanup action inside the static method. Alternatively, usage of the standalone class or static nested class that implements Runnable is also possible. Essentially, this is all we need!

Although java.lang.ref.Cleaners provides better and safer alternative to the finalizers, you should not overuse them. The AutoCloseable and try-with-resources idiom should be the preferred approach to manage resources in the majority of situations.

try (final ResourceAccessor resource = new ResourceAccessor()) {
    // Safely use the resource            
}

In these rare conditions when the lifespan of the resource is not well defined, the java.lang.ref.Cleaner is here to help. If you are curious how JDK is using java.lang.ref.Cleaner internally, please take a look at, for example, java.util.Timer or sun.nio.ch.NioSocketImpl classes.

Thursday, September 30, 2021

Chasing Java's release train: JDK-17, the next big thing

Here we go, JDK-17, the next LTS release, just became generally available. It is an important milestone for the OpenJDK for years to come but sadly, Project Loom, the most anticipated improvement of the JVM platform, was not able to make it, despite the extraordinary progress being made. Well, if you are still on JDK-8, like the majority of us, who cares, right?

Not really, for example Spring Framework had made an announcement quite recently to support JDK-17 as a baseline. It is very likely others will follow, leaving JDK-8 behind. So what is new in JDK-17?

And what is happening on the security side of things? Besides SecurityManager deprecation (JEP-411), it is worth to mention:

JDK-17 is an important release. First of all, it is going to become the de-facto choice for green field projects for the next couple of years. Second, the ones who are still on JDK-8 would migrate straight to JDK-17, skipping JDK-11 entirely (there are hardly any reasons to migrate to JDK-11 first). Consequently, the third, migration from JDK-11 to JDK-17 requires comparatively low efforts (in most cases), it is logical to expect seeing the JDK-17 kicking out JDK-11.

Are we done yet? Luckily, not at all, the JDK release train is accelerating, shifting from three years LTS releases to two years. Hopefully, this strategic decision would speed up the pace of migration, specifically for enterprises, where many are stuck with older JDKs.

And let us not forget, the JDK-18 is already in early access! It comes with the hope that Project Loom is going to be included, in some form or the other.

Thursday, July 29, 2021

Chasing Java's release train, from 8 to 16. Part 3: The avalanche of releases ...

Undoubtedly, JDK-11 was an import milestone but once the dust settled, another target appeared on the horizon, JDK-17, the next LTS release. But between those, the avalanche of new releases and features was unleashed.

JDK 12

JDK-12 didn't have too many features packed into it nonetheless it includes considerable number of improvements, especially to G1 garbage collector.

Besides the changes we have talked about, JDK-12 has delivered quite a number of the security enhancements, notably:

JDK 13

Just when the excitement after JDK-12 release faded away, the JDK-13 was ready to come along. By all means, it was a minor one from the features perspective.

A fair amount of the security enhancements in JDK-13 is certainly worth checking out.

JDK 14

Going further, the JDK-14 kept the steady pace of innovation and was bundled with quite a useful set of changes. Let us take a look at the most interesting ones.

The JDK-14 was not without the security enhancements, the most disruptive of those was probably the removal of the java.security.acl APIs (see please JDK-8191138).

JDK 15

Arguably, the JDK-15 was a major one for the reasons that many experimental and preview features had finally graduated to mainstream and became ready for production use. Let us start from those.

Besides JEP-339 (EdDSA), there are quite a few security enhancements in JDK-15, the ones that deserve mentioning are:

JDK 16

If JDK-15 qualified as a major one then JDK-16 could be easily stamped as huge, packed with new language features, tooling and GC improvements. Let us dig right in.

Many security enhancements were baked into JDK-16, just to mention a couple:

JDK 17: Mostly There

Nonetheless the JDK-17 is not out yet, it has entered Rampdown Phase Two, meaning its feature set is frozen and no further JEPs will be targeted to this release. In the upcoming part we are going to cover it right when its release is announced. Stay tuned!