https://github.com/dsyer/spring-boot-java-10
https://github.com/dsyer/spring-boot-java-10
Last synced: about 1 year ago
JSON representation
- Host: GitHub
- URL: https://github.com/dsyer/spring-boot-java-10
- Owner: dsyer
- Created: 2017-09-08T11:13:18.000Z (over 8 years ago)
- Default Branch: main
- Last Pushed: 2020-06-15T09:49:33.000Z (almost 6 years ago)
- Last Synced: 2025-03-01T02:13:30.937Z (about 1 year ago)
- Language: Java
- Size: 61.5 KB
- Stars: 36
- Watchers: 7
- Forks: 7
- Open Issues: 2
-
Metadata Files:
- Readme: README.adoc
Awesome Lists containing this project
- awesome - dsyer/spring-boot-java-10 - (<a name="Java"></a>Java)
README
Java 10 is here, and the ecosystem is adapting. If you want to run Spring Boot apps in Java 10, the good news is it works fine (mostly), so here is how to get started as quickly as possible without stumbling on the newbie errors (like I did).
== Install Java 10
You can download the http://jdk.java.net/10/[OpenJDK builds] or you can use http://sdkman.io/[SDK Man]:
```
$ sdk list java
================================================================================
Available Java Versions
================================================================================
10.0.2-oracle
> * 10.0.1-zulu
10.0.0-openjdk
...
8u131-zulu
7u141-zulu
6u93-zulu
$ sdk use java 10.0.1-zulu
```
This downloads and installs the JDK at `~/.sdkman/candidates/10.0.1-zulu` and puts it on your `PATH`:
```
$ java -version
openjdk version "10.0.1" 2018-04-17
OpenJDK Runtime Environment Zulu10.2+3 (build 10.0.1+9)
OpenJDK 64-Bit Server VM Zulu10.2+3 (build 10.0.1+9, mixed mode)
```
If you are an Eclipse user, you might want to install the Java 9 support from the https://marketplace.eclipse.org/content/java9-support-beta-oxygen[Eclipse Marketplace] (make sure you have Eclipse Oxygen 4.7 first). You don't have to run Eclipse with Java 9, but if you do you might need to change the https://wiki.eclipse.org/Configure_Eclipse_for_Java_9[ini file]. Once you have the Java 9 features installed you can add the JDK to your `Preferences -> Java -> Installed JREs`.
== Create a Spring Boot Application
You can use the source code from https://github.com/dsyer/spring-boot-java-10[this repo], or you can generate an empty project for yourself. To generate a project, go to https://start.spring.io[Spring Initializr] and generate a default blank project using Spring Boot 2.0.3. Java 10 is an option for the generator so the `pom.xml` should show the Java version:
.pom.xml
```
10
```
The Maven wrapper that comes with the generated project, and the plugin versions in the parent POM all work with Java 10, so you can build a jar file on the command line:
```
$ ./mvnw clean install
...
$ ls target/*.jar
spring-boot-java9-0.0.1-SNAPSHOT.jar
```
The jar file is executable as usual (use `...-exec.jar` if you are building from the source code of this README):
```
$ java -jar target/spring-boot-java9-0.0.1-SNAPSHOT.jar
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.0.3.RELEASE)
...
```
It doesn't do anything, so the main method just finishes and the JVM exits. All good. It might be faster than Java 8, but the effect is probably not measurable, and Java 8 performs better if you tweak it (see below).
Spring needs to use reflection, and through CGLib it results in an illegal access, which is logged as a warning on startup:
```
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.springframework.cglib.core.ReflectUtils$1 (jar:file:/home/dsyer/dev/demo/workspace/demo/target/spring-boot-java9-0.0.1-SNAPSHOT.jar!/BOOT-INF/lib/spring-core-5.0.0.BUILD-SNAPSHOT.jar!/) to method java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain)
WARNING: Please consider reporting this to the maintainers of org.springframework.cglib.core.ReflectUtils$1
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
```
You can switch this off by adding `-illegal-access=deny` to the command line (the default in Java 10 is `permit`). Here's a https://jira.spring.io/browse/SPR-15859[Spring JIRA issue] tracking changes related to this warning. You can also use Spring Boot 2.1.0 (currently in snapshots), which pulls in Spring 5.1.0 and then the warning will go away.
== Run Using an Explicit Classpath
Java 10 supports the old command line format of Java 8, which is why the executable jar just works. We can also use an explicit class path and a main method, just to be sure that it works. First we need to generate a classpath. A useful tool for that is the https://github.com/dsyer/spring-boot-thin-launcher[Thin Jar Launcher], and in particular the `ThinJarWrapper` which can be downloaded from http://repo1.maven.org/maven2/org/springframework/boot/experimental/spring-boot-thin-wrapper/1.0.12.RELEASE/spring-boot-thin-wrapper-1.0.12.RELEASE.jar[Maven Central]:
```
$ wget http://repo1.maven.org/maven2/org/springframework/boot/experimental/spring-boot-thin-wrapper/1.0.12.RELEASE/spring-boot-thin-wrapper-1.0.12.RELEASE.jar
```
and then we can run the wrapper with some command line arguments to make it print the classpath for the current project:
```
$ CP=`java -jar spring-boot-thin-wrapper-1.0.12.RELEASE.jar --thin.archive=. --thin.classpath`
```
Then we can run our app like this (assuming the default class name from start.spring.io):
```
$ java -cp target/classes/:$CP com.example.demo.DemoApplication
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.0.3.RELEASE)
...
```
NOTE: You can use any tool you like to list the dependencies of your app and format them as a classpath, just get it in the right format and it will work the same.
At this point we might decide to repackage the jar file into a thin (module) format, removing the `spring-boot-maven-plugin` from the `pom.xml` and re-building the project. Then we can use the jar itself on the classpath, instead of the `target/classes` directory:
```
$ ./mvnw clean install
$ java -cp target/spring-boot-java9-0.0.1-SNAPSHOT.jar:$CP com.example.demo.DemoApplication
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.0.3.RELEASE)
...
```
== Running Using Jigsaw
Java 10 has other features for running applications. So instead of using the classpath we can use the Jigsaw (module system) features, relying on "automatic modules" to turn all the dependency jars into modules. The command line is slightly different, but the result is the same (except that the WARN logs are not emitted):
```
java -p target/spring-boot-java9-0.0.1-SNAPSHOT.jar:$CP --add-modules ALL-DEFAULT -m spring.boot.java9/com.example.demo.DemoApplication
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot ::
...
```
The app runs as before, but note that the Spring Boot version information is not printed because it is not accessible the same way from inside Spring Boot. Here are the pieces of the command line, blow by blow...
=== Module Path
The module path is `-p` (not `-cp`) but it is in the same format as a classpath. Automatic modules only work as jars, which is why we built the non-executable jar for the app, instead of using `target/classes`. If you try using a directory in the module path that isn't a module you will find that it is not automatically converted to a module, and there will be an error on the command line:
```
$ java -p target/classes:$CP --add-modules ALL-DEFAULT -m spring.boot.java9/com.example.demo.DemoApplication
Error occurred during initialization of boot layer
java.lang.module.FindException: Module demo not found
```
=== Adding JDK Modules
We need to add additional modules to the command line since the default is a much narrower subset that won't work with Spring Boot. If we ommit the `--add-modules ALL-DEFAULT` it breaks:
```
$ java -p target/spring-boot-java9-0.0.1-SNAPSHOT.jar:$CP -m spring.boot.java9/com.example.demo.DemoApplication
Exception in thread "main" java.lang.IllegalArgumentException: Cannot instantiate interface org.springframework.context.ApplicationContextInitializer : org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer
at spring.boot@2.0.2.RELEASE/org.springframework.boot.SpringApplication.createSpringFactoriesInstances(SpringApplication.java:439)
at spring.boot@2.0.2.RELEASE/org.springframework.boot.SpringApplication.getSpringFactoriesInstances(SpringApplication.java:418)
at spring.boot@2.0.2.RELEASE/org.springframework.boot.SpringApplication.getSpringFactoriesInstances(SpringApplication.java:409)
at spring.boot@2.0.2.RELEASE/org.springframework.boot.SpringApplication.(SpringApplication.java:266)
at spring.boot@2.0.2.RELEASE/org.springframework.boot.SpringApplication.(SpringApplication.java:247)
at spring.boot@2.0.2.RELEASE/org.springframework.boot.SpringApplication.run(SpringApplication.java:1245)
at spring.boot@2.0.2.RELEASE/org.springframework.boot.SpringApplication.run(SpringApplication.java:1233)
at demo@0.0.1-SNAPSHOT/com.example.demo.DemoApplication.main(DemoApplication.java:10)
Caused by: java.lang.NoClassDefFoundError: java/sql/SQLException
at spring.beans@5.0.6.RELEASE/org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:176)
at spring.boot@2.0.2.RELEASE/org.springframework.boot.SpringApplication.createSpringFactoriesInstances(SpringApplication.java:435)
... 7 more
Caused by: java.lang.ClassNotFoundException: java.sql.SQLException
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:582)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:185)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:496)
... 9 more
```
=== The Main Class
With a classpath, or with Java 8, we provide the main class as a command line argument, unqualified with no option flag.
With a module path, i.e. using Jigsaw, we need to provide a `-m ...` which is in the form `/`. If you forget that, you get a rather unhelpful error:
```
$ java -p target/spring-boot-java9-0.0.1-SNAPSHOT.jar:$CP --add-modules ALL-DEFAULT com.example.demo.DemoApplication
Error: Could not find or load main class com.example.demo.DemoApplication
Caused by: java.lang.ClassNotFoundException: com.example.demo.DemoApplication
```
The module name is the "automatic" one (the jar name with "." instead of "-").
== Adding More Features
With Spring Boot it's easy to add features. A basic webapp with Tomcat can be created just by adding `spring-boot-starter-web` to your `pom.xml`:
.pom.xml
```
org.springframework.boot
spring-boot-starter-web
```
or you can add the Actuator using `spring-boot-starter-actuator`
.pom.xml
```
org.springframework.boot
spring-boot-starter-actuator
```
Remember to set `endpoints.default.web.enabled=true` if you want to see the Actuator endpoints in the webapp by default. E.g:
```
$ java -p target/spring-boot-java9-0.0.1-SNAPSHOT.jar:$CP --add-modules ALL-DEFAULT -m spring.boot.java9/com.example.demo.DemoApplication --endpoints.default.web.enabled=true
...
2017-09-08 11:49:28.011 INFO 22102 --- [ main] b.e.w.m.WebEndpointServletHandlerMapping : Mapped "{[/application/mappings],methods=[GET],produces=[application/vnd.spring-boot.actuator.v2+json || application/json]}" onto public java.lang.Object org.springframework.boot.endpoint.web.mvc.WebEndpointServletHandlerMapping$OperationHandler.handle(javax.servlet.http.HttpServletRequest,java.util.Map)
2017-09-08 11:49:28.011 INFO 22102 --- [ main] b.e.w.m.WebEndpointServletHandlerMapping : Mapped "{[/application/health],methods=[GET],produces=[application/vnd.spring-boot.actuator.v2+json || application/json]}" onto public java.lang.Object org.springframework.boot.endpoint.web.mvc.WebEndpointServletHandlerMapping$OperationHandler.handle(javax.servlet.http.HttpServletRequest,java.util.Map)
2017-09-08 11:49:28.011 INFO 22102 --- [ main] b.e.w.m.WebEndpointServletHandlerMapping : Mapped "{[/application/status],methods=[GET],produces=[application/vnd.spring-boot.actuator.v2+json || application/json]}" onto public java.lang.Object org.springframework.boot.endpoint.web.mvc.WebEndpointServletHandlerMapping$OperationHandler.handle(javax.servlet.http.HttpServletRequest,java.util.Map)
...
```
== JLink
https://docs.oracle.com/javase/9/tools/jlink.htm#JSWOR-GUID-CECAC52B-CFEE-46CB-8166-F17A8E9280E9[JLink] is a JDK tool that creates a self-contained binary image for a Java program (no need for a JRE or JDK at runtime). It works with Jigsaw modules, but only with explicit modules, not automatic ones, and most of the dependencies in a Spring Boot application only provide automatic modules. So this is the kind of thing you will see if you try to build an image:
```
$ cp target/spring-boot-java9-0.0.1-SNAPSHOT.jar target/demo.jar
$ jlink -p target/demo.jar:$CP:$JAVA_HOME/jmods --add-modules demo --output jre
Error: automatic module cannot be used with jlink: snakeyaml from file:///.../.m2/repository/org/yaml/snakeyaml/1.19/snakeyaml-1.19.jar
```
== Ahead of Time Compilation (AOT)
http://openjdk.java.net/jeps/295[AOT] is an (unsupported) feature of Java 9. There are plenty of "Hello World" examples out there, which seem to show improved startup performance. Nothing much to show how to work with non-trivial apps. Here's a recipe:
```
$ CP=`java -jar spring-boot-thin-wrapper-1.0.12.RELEASE.jar --thin.archive=target/spring-boot-java9-0.0.1-SNAPSHOT.jar --thin.classpath`
$ java -XX:DumpLoadedClassList=target/app.classlist -cp $CP com.example.demo.DemoApplication
$ jaotc --output target/libDemo.so -J-cp -J$CP `cat target/app.classlist | sed -e 's,/,.,g'`
$ java -XX:AOTLibrary=target/libDemo.so -cp $CP com.example.demo.DemoApplication
```
You can add `-XX:+PrintAOT` to the `java` command line to see the classes being loaded from the shared library. Without that extra logging it's about 15% faster (500ms compared to 600ms). Adding Tomcat and actuators shows 2000ms compared to 2400. But that was without any other command line arguments; once we add `-noverify -XX:TieredStopAtLevel=1` the improvement isn't so dramatic (1490ms vs 1540ms) with actuator, or without (560ms vs 630ms).
The above commands only compile the class from the boot classpath though (i.e. the JRE), so maybe including the application classes would speed things up enough to make it worthwhile. We have to use the Oracle JDK and some extra flags to get the app classes (`-XX:+UnlockCommercialFeatures -XX:+UseAppCDS`). Unfortunately when you do that `jaotc` fails because it can't compile some of the classes (AOP proxies, and ones that refer to stuff that isn't on the classpath). This is kind of a showstopper for a Spring Boot app because we use proxies a lot and also compile in references to classes that aren't used at runtime. Even if there was a way to automatically filter out the classes that would fail this way, the limitations listed in the Oracle documentation and associated articles make it seem unlikely that it would be hugely successful in practice.
== Java 8
Running with Java 8 (no AOT) is the same or faster, though, so there's not much incentive to use Java 9 (520ms for the vanilla app, 1490ms with actuator). Spring Boot 1.5.9 makes it a bit faster too, so (460ms and 1300ms).
=== CDS
http://openjdk.java.net/jeps/310[Common Data Sharing] extends to application classes in Java 10. It's quite a bit quicker on startup (but Java 10 is slower, so Java 8 might be comparable overall):
```
$ CP=`java -jar spring-boot-thin-wrapper-1.0.12.RELEASE.jar --thin.archive=target/spring-boot-java9-0.0.1-SNAPSHOT.jar --thin.classpath`
$ java -Xshare:off -XX:+UseAppCDS -XX:DumpLoadedClassList=hello.lst -cp target/classes/:$CP com.example.demo.DemoApplication
$ java -Xshare:dump -XX:+UseAppCDS -XX:SharedClassListFile=hello.lst -XX:SharedArchiveFile=hello.jsa -cp $CP com.example.demo.DemoApplication
$ java -Xshare:on -XX:+UseAppCDS -XX:SharedArchiveFile=hello.jsa -cp $CP com.example.demo.DemoApplication
```