Cloud native adoption is now mainstream. Kubernetes usage in production has soared to 82% among container[1], and Java applications constitute a substantial share within those clusters. As Kubernetes adoption continues to grow, the need for monitoring, managing and observing Java applications in containers have become more crucial…
In cloud-native architectures, services are typically decomposed into many microservices running across multiple pods. As a result, effective troubleshooting and performance analysis depend on correlating telemetry data (logs, metrics, and traces) across services and performing cross-JVM analysis to diagnose bottlenecks.
While managed Kubernetes platforms provide infrastructure-level metrics such as CPU, memory, logs, and pod health, they do not natively expose JVM-level diagnostic data. JDK Flight Recorder (JFR) is a JVM specific tool that provide these diagnostics such as garbage collection behaviour, allocation profiling, lock contention, safe point analysis, CPU profile shifts, and thread state transitions.
Java Management Service (JMS) in OCI leverages the JFR capabilities to address this gap by providing secure, fleet-scale orchestration of JFR across distributed Kubernetes environments.
Traditional approach to get JVM diagnostics is by accessing the running JVM directly via JMX or command-line tools like jcmd. However, modern microservices architecture introduces following complexities:
These complexities introduce several operational challenges:
jlink-generated runtimes, which may limit the availability of diagnostic utilities within the container.JMS, an OCI native service, provides monitoring and management for Java workloads. It extends OCI observability by enabling secure, scalable JFR captures across Kubernetes clusters without exposing the JMX endpoints or requiring direct container access. It is implemented as a plugin to the Oracle Management Agent (OMA)[3] and key capabilities includes:
JMS enables centralized capture of JFR recordings from a specific container or across selected application containers within a cluster. Through the JMS console or APIs, administrators can select the target workload, configure recording parameters, and initiate a JFR capture request. Upon completion, the recordings from each container are automatically exported to the designated Object Storage bucket. These recordings can then be analysed to diagnose performance bottlenecks, optimize resource utilization, and gain actionable insights into Java application behaviour.
Figure 1: Architecture Overview
JMS integrates with the Oracle Management Agent (OMA) service, which is deployed in the Kubernetes cluster using the OCI Kubernetes Monitoring Solution[2]. The JMS plugin runs within the OMA pod and interacts with application pods to collect Java telemetry data and perform JFR operations.
The JMS plugin communicates with the JMS service running in OCI to transmit telemetry data and manage JFR capture requests. Using Kubernetes client APIs, the plugin discovers Java workloads in the cluster, retrieves Java runtime usage details, and executes commands within the application containers to start and stop JFR recordings. The captured telemetry and JFR data are sent securely to JMS service and Object storage respectively. The JMS console displays this data, providing insights into Java runtime versions and application details across the Kubernetes environment.
Administrators can monitor Java application in active containers and trigger or schedule JFRs.
Operational workflow:
Figure 2: JMS Console displaying active Java containers and JFR controls
In case of an incident, administrators can trigger JFR across all replicas in a single action during live production troubleshooting.
JMS can be integrated with OCI Monitoring to trigger JFR based on real time Kubernetes metrics.
Operational workflow:
(Phase enhancements are planned to reduce trigger latency (~ 10 minutes) for near-immediate recordings)
Automatically capture JFR for a fixed duration after each production deployment and detect performance regression early.
Operational workflow:
Run JFR for 48–72 hours after and compare allocation rate, thread states, blocking time, CPU profile shifts. This reduces MTTR and prevents regression from surfacing an alert noise.
As Kubernetes adoption accelerates, infrastructure-level observability is no longer enough for Java workloads. Organizations need secure JVM intelligence embedded into their cloud operations model. With JMS, OCI provides a centralized framework for Java runtime governance and fleet-scale JFR orchestration across cloud native environments.
Java 26 is getting all packaged up to be shipped worldwide! As with every release of the JDK there are a number of new features, improvements, changes in behavior, and more developers should be aware of before upgrading. In this episode of the Inside Java Newscast we will review all the noteworthy changes coming in Java 26 that will impact developers.
Links to JDK Enhancement Proposals (JEPs):
Links to JDK Bug Systems (JBS) tickets:
Make sure to also check the Duke’s Corner podcast on dev.java.
For more episodes, check out Inside Java, our YouTube playlist, and follow @Java on Twitter.
Contact us here.
]]>]]>
The OpenJDK Quality Group is promoting the testing of FOSS projects with OpenJDK builds as a way to improve the overall quality of the release. This heads-up is part of a regular communication sent to the projects involved. To learn more about the program, and how-to join, please check here.
BCP 47, first introduced in 1995, defines tags to identify languages and its primary subtag (the first of potentially multiple subtags) is usually drawn from the much older ISO 639-1 standard. There are exceptions, though, among them these three languages:
Before JDK 17, Java treated the ISO-639 tags of these three languages as canonical and mapped the BCP 47 tags to them (see Locale’s documentation under Legacy language codes):
static void printLocales(String iso, String bcp) {
System.out.println(Locale.forLanguageTag(iso) + " | " + Locale.forLanguageTag(bcp));
}
// on JDK < 17, ISO 639 tags are preferred:
public static void main(String[] args) {
printLocales("iw", "he"); // ~> "iw | iw"
printLocales("in", "id"); // ~> "in | in"
printLocales("ji", "yi"); // ~> "ji | ji"
}
This changed in JDK 17 and since then the default behavior was to map to BCP 47 tags:
// on JDK 17+, BCP 47 tags are preferred:
public static void main(String[] args) {
printLocales("iw", "he"); // ~> "he | he"
printLocales("in", "id"); // ~> "id | id"
printLocales("ji", "yi"); // ~> "yi | yi"
}
To allow applications to update to Java 17 and later without requiring immediate code changes, the temporary system property java.locale.useOldISOCodes was introduced.
Setting it to true reverted to the behavior before Java 17.
JDK 25 deprecated java.locale.useOldISOCodes and JDK 27 will remove it.
Setting it will result in a warning:
WARNING: The system property "java.locale.useOldISOCodes" is no longer supported. Any specified value will be ignored.
Code that relies on java.locale.useOldISOCodes needs to be updated to consistently expect BCP 47 tags when querying Locale instances.
It will remain possible to use the ISO 639 tags to instantiate Locale instances for Hebrew, Indonesian, and Yiddish.
Java’s checked exceptions are both an integral part of the language and one of its most contested features. Whether their introduction was a mistake and whether they should all be turned unchecked are frequently discussed topics but since the former is not overly relevant and the latter unlikely, this conversation isn’t moving Java forward. Instead, let’s talk about specific issues with checked exceptions and what could be done about them - from (entirely speculative) language changes to (marginally realistic) JDK/library evolution to stylistic changes.
Links:
Either:
Make sure to also check the Duke’s Corner podcast on dev.java.
For more episodes, check out Inside Java, our YouTube playlist, and follow @Java on Twitter.
Contact us here.
]]>JDK 25 has arrived, bringing a major set of performance gains over JDK 21—often letting your existing, unchanged Java applications run faster right away. In this talk, we’ll dive into 13 concrete performance improvements delivered between JDK 21 and JDK 25 across the standard libraries, the JIT compiler, and garbage collectors.
Along the way, you’ll get an inside look at the design tradeoffs behind these optimizations and how JDK engineers evaluate performance in the real world—where platforms differ and optimization goals can conflict.
We’ll also spotlight one of the most exciting new additions: the preview feature Stable Value, which lets a field combine key benefits of both mutable and immutable data. You’ll learn how Stable Value works, what kinds of speedups it can unlock, and how you can start taking advantage of it today.
Recorded at Jfokus 2026.
]]>
Lazily initializing fields in Java is error-prone and undermines constant-folding. JDK 26 comes with JEP 526, which previews LazyConstant, a type that lazily initializes a value through a given Supplier. It executes that supplier at most once successfully and then assigns the value to a field annotated with @Stable, which allows constant folding. This API is also a poster child for how OpenJDK develops and evolves features.
Links:
Make sure to also check the Duke’s Corner podcast on dev.java.
For more episodes, check out Inside Java, our YouTube playlist, and follow @Java on Twitter.
Contact us here.
]]>Java 26 is getting all packaged up to be shipped worldwide! As with every release of the JDK there are a number of new features, improvements, changes in behavior, and more that developers should be aware of before upgrading. In this episode of the Inside Java Newscast we will review all the noteworthy changes coming in Java 26 that will impact developers.
Make sure to check the show-notes.
]]>Java 26, which will be released on March 17th, will debut a number of changes to the java.net.http.HttpClient, which was originally added in JDK 11. In this article we will review these changes and how developers can take advantage of them, and what they might need to consider when upgrading to Java 26 and beyond.
JEP 517 adds support for HTTP/3 to the HttpClient. It’s important to note that support is only being added, but the default will remain HTTP/2, which has been the case since the HttpClient was initially added in JDK 11.
The preferred protocol version can be set at the HttpClient level:
var client = HttpClient.newBuilder().version(HttpClient.Version.HTTP_3).build();
And at the HttpRequest level:
var request = HttpRequest.newBuilder(URI.create("https://openjdk.org/"))
.version(HttpClient.Version.HTTP_3).GET().build();
The HttpClient offers options for navigating the transition between HTTP/1.1 and HTTP/2 to HTTP/3 covered below.
Part of the reason for not setting HTTP/3 as the default for HttpClient, is because HTTP/3 uses a new transport protocol, QUIC, an update from the TCP protocol used by HTTP/1.1 and HTTP/2, and HTTP/3 is not as widely deployed as HTTP/1.1 or HTTP/2. However while QUIC offers many advantages, the addition of a new transport protocol does bring some complications. Specifically there’s no way to know which transport protocol is being used ahead of time. To that end, below are four strategies that can be used with the HttpClient that can handle working with HTTP/1.1, HTTP/2, and HTTP/3 services.
HTTP/3 should be used when available, and with services migrating towards HTTP/3 an optimistic approach can be taken to attempt to connect using HTTP/3 first, and only fall back to HTTP/2 or HTTP/1.1 if a connection cannot be established. This can be done by not setting the protocol version at the client level, but setting it at the request level:
var client = HttpClient.newBuilder().build();
var request = HttpRequest.newBuilder(URI.create("https://openjdk.org/")
.version(HttpClient.Version.HTTP_3).GET().build();
Sending requests in serial, like in the above example, can increase latency, espeically when a fallback is likely. Alternatively HTTP/3 and HTTP/2 or HTTP/1.1 requests can be sent in parallel, and the first one that succeeds will be used. This can be accomplished by setting the preferred version to HTTP/3 at the client level, but not setting a preferred version at the request level:
var client = HttpClient.newBuilder().version(HttpClient.Version.HTTP_3).build();
var request = HttpRequest.newBuilder(URI.create("https://openjdk.org/")).GET().build();
If it’s likely the service you are connecting to does not support HTTP/3, then a pessimistic approach could be taken, and send a HTTP/1.1 or HTTP/2 request first, and only switching to HTTP/3 if the serivce says its available. This can be done by setting the Http3DiscoveryMode.ALT_SVC option:
var client = HttpClient.newBuilder().build();
var request = HttpRequest.newBuilder(URI.create("https://openjdk.org/"))
.setOption(HttpOption.H3_DISCOVERY, Http3DiscoveryMode.ALT_SVC).GET().build();
The last strategy would be to only use HTTP/3 and not fall back if the server does not reply with HTTP/3. This can be done by setting the Http3DiscoveryMode.HTTP_3_URI_ONLY option:
var client = HttpClient.newBuilder().build();
var request = HttpRequest.newBuilder(URI.create("https://openjdk.org/"))
.setOption(HttpOption.H3_DISCOVERY, Http3DiscoveryMode.HTTP_3_URI_ONLY).GET().build();
A new static method, java.net.HttpRequest.BodyPublishers.ofFileChannel(FileChannel chan, long position, long size) has been added. ofFileChannel(FileChannel chan, long position, long size) allows for a specific region of a file to be uploaded. For large files, this can avoid having to read the entire file into memory, and can be leveraged to slice a file it chunks and uploaded in parallel.
https://bugs.openjdk.org/browse/JDK-8329829
During the setup of new connections, HttpClient now uses the signature schemes and named groups configured on SSLParameters, like in the example below, when negotiating the TLS handshake. Previously these configured values were ignored.
final SSLParameters sslParameters = new SSLParameters();
sslParameters.setNamedGroups(new String[]{namedGroup});
HttpClient.newBuilder().sslContext(sslBundle.createSslContext())
.sslParameters(sslParameters).build();
https://bugs.openjdk.org/browse/JDK-8367112
The HttpClient request timeout set using java.net.http.HttpRequest.Builder::timeout previously applied only until the response headers were received. Its scope has now been extended to also cover the consumption of the response body, if present. In most cases this should align with expected behavior of canceling a long running connection, but if your application frequently deals with large response bodies and depends upon the previous behavior, you will need to update your timeout values accordingly.
https://bugs.openjdk.org/browse/JDK-8208693
The HttpClient will no longer send a Content-Length header on HTTP/1.1 request for non-POST/PUT methods which contain no body. This follows RFC9110 semantics. If your application depends on the Content-Length header, it can be added using HttpRequest.Builder, and by updating the jdk.httpclient.allowRestrictedHeaders property to include Content-Length.
https://bugs.openjdk.org/browse/JDK-8358942
Previously the java.net.http.HttpRequest.BodyPublishers::ofFile(Path) method could throw a java.io.NoSuchFileException if the provided Path was not associated with the default file system. This inconsistency has been removed by mapping these to java.io.FileNotFoundException, in line with the ofFile(Path) API specification.
https://bugs.openjdk.org/browse/JDK-8358688
java.net.HttpCookie has been updated to correctly return the value of Max-Age when when calling getMaxAge() for cookies that have both Expires and Max-Age attributes. This follows RFC 6265 specifies that the Max-Age attribute should take precedence over the Expires attribute.
https://bugs.openjdk.org/browse/JDK-8351983
Java 26 ended up being a pretty important release for HttpClient users. With HTTP/3 support being added as well as several other updates. If you’d like to learn more about the HttpClient, and all the other features coming in Java 26 and beyond, consider attending JavaOne March 17-19th in Redwood City, California. Tickets are on sale now as well as the full session list at: https://javaone.com.
The OpenJDK Quality Group is promoting the testing of FOSS projects with OpenJDK builds as a way to improve the overall quality of the release. This heads-up is part of the quality outreach sent to the projects involved. To learn more about the program, and how-to join, please check here.
On macOS, the default JavaFX rendering pipeline has been switched to Metal since JavaFX 27 Early Access (EA) build 3. Metal provides improved performance and better compatibility on modern hardware.
The Metal pipeline was implemented under JDK-8271024 in JavaFX 26 as an optional pipeline, with the OpenGL-based ES2 pipeline as the default. Starting with JavaFX 27 EA build 3, Metal is now the default rendering pipeline on macOS.
JavaFX 27 EA builds are now available for download and testing. You can share your feedback on the openjfx-dev list (registration required), through JBS or bugreport.java.com.
If you need to revert to the previous ES2 pipeline, you can do so by adding the option -Dprism.order=es2 to your Java command.
References to metal blogs published earlier: