Starting Jetty using JPMS
Jetty modules are proper JPMS modules: each Jetty module has a module-info.class
file.
This makes possible to run Jetty from the module-path, rather than the class-path.
To start Jetty on the module-path rather than the class-path, it is enough to add the --jpms
option to the command line, for example:
$ java -jar $JETTY_HOME/start.jar --jpms
The When running on the module-path using the Therefore, you will have two JVMs running: one that runs Forking a second JVM may be avoided as explained in this section. |
When Jetty is started in JPMS mode, all JPMS modules in the module-path are added to the set of JPMS root modules through the JVM option --add-modules=ALL-MODULE-PATH
.
For a *.jar
file that is not a JPMS module, but is on the module-path, the JVM will assume internally it is an automatic JPMS module, with a JPMS module name derived from the *.jar
file name.
As an alternative to adding the --jpms
option to the command line, you can use a custom Jetty module to centralize your JPMS configuration, where you can specify additional JPMS directives.
Create the $JETTY_BASE/modules/jpms.mod
file:
[description] JPMS Configuration Module [ini] --jpms [jpms] # Additional JPMS configuration.
The [ini]
section with --jpms
is equivalent to passing the --jpms
option to the command line (see also this section).
The [jpms]
section allows you to specify additional JPMS configuration, for example additional --add-modules
options, or --add-opens
options, etc. (see also this section).
Then enable it:
$ java -jar $JETTY_HOME/start.jar --add-modules=jpms
Now you can start Jetty without extra command line options, and it will start in JPMS mode because you have enabled the jpms
module.
Advantages of JPMS
The main advantage of running Jetty in JPMS mode is that it provides early error reporting if a dependency is missing, especially in case of custom Jetty modules.
If a Jetty module requires a specific library *.jar
file, and the *.jar
file is missing, this will be detected early when running in JPMS mode, since the *.jar
file would be a JPMS module, and the JPMS module graph resolution would detect this problem as the JVM starts up.
When running in class-path mode, this problem can only be detected when the library classes are loaded, therefore much later at run-time, when an HTTP request arrives and its processing requires to load the library classes.
Similarly, duplicate *.jar
files, of possibly different versions, are not allowed by JPMS.
This is again detected early, at JVM startup, when running in JPMS mode, rather than passing completely unnoticed when running in class-path mode, where classes may be loaded from either of those *.jar
files, possibly from the wrong version.
Another advantage of running Jetty in JPMS mode is that JPMS provides strong encapsulation.
This means that it is not possible to use reflection to access non-exported classes in JPMS modules within the ModuleLayer
hierarchy.
This is enforced at the JVM level, not at the application level through, for example, a custom ClassLoader
that performs class loading filtering.
It also means that it is not possible to use reflection to access sibling ModuleLayer
s.
For example a Jakarta EE 9 web application would not be able to use reflection to access a sibling Jakarta EE 10 web application.
Server Module-Path and ModuleLayers
When the Jetty server is started in JPMS mode, there will be two JVMs: one started by you with java -jar $JETTY_HOME/start.jar
and one forked by the Jetty start mechanism.
Remember that you can avoid to fork a second JVM, as described in this section. |
The forked JVM is started with java --module-path $JETTY_HOME/lib/jetty-server-<version>.jar:...
, and therefore has the server libraries in its module-path, loaded by the System ClassLoader, in the boot ModuleLayer
:
When a Jakarta EE Jetty module is enabled, for example the ee10-webapp
and/or the ee10-deploy
module, the forked JVM is configured as follows:
Note how the parent of the Jetty EE 10 ClassLoader
is the System ClassLoader
, and how the parent of the Jetty EE 10 ModuleLayer
is the Boot ModuleLayer
.
When different versions of the Jakarta EE Jetty modules are enabled, the forked JVM is configured as follows:
Note how the Jetty EE ClassLoaders
s are siblings, and both have the System ClassLoader
as parent.
Note also how the Jetty EE ModuleLayer
s are siblings, and both have the Boot ModuleLayer
as parent.
Advanced JPMS Configuration
Web applications may need additional services from the Servlet Container, such as JDBC DataSource
references or JTA UserTransaction
references.
For example, for JDBC it is typical to store, in JNDI, a reference to the connection pool’s DataSource
or directly a reference to the JDBC driver’s DataSource
(for example, org.postgresql.ds.PGConnectionPoolDataSource
).
Jetty needs to be able to instantiate those classes and therefore needs to be able to load those classes and all their super-classes, among which includes javax.sql.DataSource
.
When Jetty runs on the class-path, this is easily achieved by using a custom module as explained in this section.
However, when running on the module-path, things are quite different.
When Jetty tries to load, for example, class org.postgresql.ds.PGConnectionPoolDataSource
, it must be in a JPMS module that is resolved in the run-time module graph.
Furthermore, any dependency, for example classes from the java.sql
JPMS module, must also be in a module present in the resolved module graph.
Thanks to the fact that when Jetty starts in JPMS mode the --add-modules=ALL-MODULE-PATH
option is added to the JVM command line, every *.jar
file in the module-path is also present in the module graph.
There are now two cases for the postgresql-<version>.jar
file: either it is a proper JPMS module, or it is an automatic JPMS module (either an explicit automatic JPMS module with the Automatic-Module-Name
attribute in the manifest, or an implicit automatic JPMS module whose name is derived from the *.jar
file name).
If the postgresql-<version>.jar
file is a proper JPMS module, then there is nothing more that you should do: the postgresql-<version>.jar
file is in the module-path, and all the modules in the module-path are in the module graph (thanks to --add-modules=ALL-MODULE-PATH
), and any dependency declared in the module-info.class
will be added to the module graph.
Otherwise, postgresql-<version>.jar
file is an automatic module, and will likely have a dependency on the JDK-bundled java.sql
JPMS module.
Fortunately, when Jetty starts in JPMS mode, the --add-modules=ALL-DEFAULT
option is also added to the JVM command line, so that all the Java JPMS modules are added to the module graph, and this would include the java.sql
JPMS module.
Thanks to the fact that Jetty starts by default adding --add-modules=ALL-DEFAULT,ALL-MODULE-PATH
, it is enough that you put custom libraries (and their dependencies) in the module-path, and you should not need any extra JPMS configuration.
However, you can explicitly configure JPMS directives in a custom Jetty module, for example to open or patch a JPMS module.
Using the postgresql.mod
introduced in this section as an example, modify your custom Jetty module and add the JPMS directives you need, like so:
[jpms] add-modules: <module>(,<module>)* patch-module: <module>=<file>(<path-separator><file>)* add-opens: <source-module>/<package>=<target-module>(,<target-module>)* add-exports: <source-module>/<package>=<target-module>(,<target-module>)* add-reads: <source-module>=<target-module>(,<target-module>)*
The Jetty JPMS directives are equivalent to the JDK JPMS directives.
The [jpms]
section is only used when Jetty is started on the module-path.