# BellSoft Liberica JDK
https://bell-sw.com
BellSoft provides Liberica JDK — certified, supported OpenJDK builds for Linux, Windows, macOS and embedded platforms. Includes Java 8, 11, 17, 21 and more.
## Products
- [10 Things to Consider Before Renewing Oracle License | BellSoft Java](https://bell-sw.com/10-things-to-consider-before-renewing-oracle-license/): Learn how Oracle's changing Java licensing models impact your business. Download our comprehensive guide to make informed decisions about renewing your Oracle license.
- [20 Questions to Ask Your Potential OpenJDK Vendor | BellSoft Java](https://bell-sw.com/20-questions-to-ask-your-potential-open-jdk-vendor/): Confused about selecting the right OpenJDK distribution provider? Download our guide with 20 essential questions to make an informed decision.
- [Page Not Found | BellSoft Java](https://bell-sw.com/404/):
- [Discover the Seven Key Benefits of Liberica JDK | BellSoft Java](https://bell-sw.com/7-reasons-to-choose-liberica-jdk/): Looking for a Oracle Java Alternative? Download our guide to explore the seven key benefits of Liberica JDK, a 100% open-source and TCK-verified Java runtime.
- [BellSoft Java Applications Developers highly qualified IT experts | BellSoft Java](https://bell-sw.com/about/): We are a team of highly qualified IT industry professionals with vast experience in building software products and technologies. We work for the largest multinational companies. We are contributors to the leading software technologies.
- [Alpaquita container images | BellSoft Java](https://bell-sw.com/alpaquita-containers/): BellSoft provides ready Alpaquita containers on Docker Hub, GHCR, MCR, GCR and Amazon ECR.
- [Alpaquita Containers for Spring Boot | BellSoft Java](https://bell-sw.com/alpaquita-containers-for-spring-boot-apps/): Alpaquita Containers for Spring Boot applications help to improve performance up to 30 %.
- [Alpaquita Linux — A Minimal and Secure Linux Distribution for Java, Cloud, and Containers](https://bell-sw.com/alpaquita-linux/): Alpaquita Linux is the Alpine-based build fine-tuned for Java with glibc and musl support. Ideal for cloud deployment, containers, and native images.
- [Technical Support for Alpaquita Linux | Bellsoft](https://bell-sw.com/alpaquita-support/): With the BellSoft support team, you can focus on deploying infrastructure or services with the utmost level of security in your containerized project.
- [Alpaquita End User License Agreement](https://bell-sw.com/alpaquita_eula/):
- [BellSoft Hardened Container Images | BellSoft Java](https://bell-sw.com/bellsoft-hardened-container-images/): BellSoft provides hardened container images designed for production environments that require enhanced security, predictable vulnerability management, and transparent build processes. These minimal, regularly updated images come with a comprehensive Security Advisory and represent a unified vendor approach to container hardening.
- [BellSoft Hardened Images - Near Zero CVE Container Images | BellSoft Java](https://bell-sw.com/bellsoft-hardened-images/): Deploy secure minimized container images with near-zero CVEs. BellSoft Hardened Images deliver continuous security monitoring, continuous rebuilds, and easy compliance.
- [BellSoft Hardened Container Images | BellSoft Java](https://bell-sw.com/bellsoft-hardened-images/vulnerability-comparisons/): BellSoft provides hardened container images designed for production environments that require enhanced security, predictable vulnerability management, and transparent build processes. These minimal, regularly updated images come with a comprehensive Security Advisory and represent a unified vendor approach to container hardening.
- [Careers at BellSoft | BellSoft Java](https://bell-sw.com/careers/): BellSoft is a major OpenJDK contributor providing Progressive Java Runtime and other tools for Java apps for modern architectures and cloud.
- [Alpaquita vs Alpine comparison | BellSoft Java](https://bell-sw.com/choose-an-optimal-linux-for-the-cloud/): Compare the key features of two lightweight Linux distributions, Alpine and Alpaquita
- [Cloud Native Platform | BellSoft Java](https://bell-sw.com/cloud-native-platform/): Alpaquita Cloud Native Platform is the solution for cloud-native Java apps including lightweight Linux and Java runtime, Java tools, and support.
- [Comparison Matrix of Popular Linux Distributions | BellSoft Java](https://bell-sw.com/comparison-of-popular-linux-distributions-for-the-cloud/): Confused about which Linux distro to choose for your Java environment? Download our comprehensive comparison table to find the perfect fit based on size, support, and features.
- [Bellsoft’s Content Library](https://bell-sw.com/content-library/): Explore a diverse collection of guides, product briefs, e-books, webinars, and videos, all crafted to enhance your understanding and skills in Java programming.
- [Join the raffle and win a Steam Deck | BellSoft Java](https://bell-sw.com/devnexus/): Fill in the form to join a raffle for winning a PlayStation 5
- [Join the raffle and win a Beats Studio Pro | BellSoft Java](https://bell-sw.com/devopsday-los-angeles/): Fill in the form to join a raffle for winning a Beats Studio Pro
- [Join the raffle and win a Beats Studio Pro | BellSoft Java](https://bell-sw.com/devopsdays-chicago/): Fill in the form to join a raffle for winning a Beats Studio Pro
- [Complete our survey for a chance to win a Steam Deck | BellSoft Java](https://bell-sw.com/devoxx-survey/): Complete our survey to join a raffle for winning a Steam Deck
- [How to migrate to JDK with minimum effort | BellSoft Java](https://bell-sw.com/effortless-java-upgrade-made-possible/): Discover a solution transforming JDK upgrade into a smooth journey with minimum expenses
- [BellSoft at Linaro Connect 19 BKK | BellSoft Java](https://bell-sw.com/events/2019/02/13/Events-Linaro-Connect-BKK/): BellSoft at Linaro Connect 19 BKK Dmitry Chuyko, BKK19-203 JVM on ARM. From sensor to cloud A variety of Java virtual machines on ARM have been around for a long time.
- [BellSoft at JBCNConf in Barcelona | BellSoft Java](https://bell-sw.com/events/2019/04/11/Events-JBCNConf/): BellSoft at JBCNConf in Barcelona Dmitry Chuyko, Do not put all eggs in one container Microservice architecture and containerization have become the standards of modern application development.
- [BellSoft at Oracle CodeOne 2019 in San Francisco | BellSoft Java](https://bell-sw.com/events/2019/06/05/Events-CodeOne2019/): BellSoft at Oracle CodeOne 2019 Dmitry Chuyko and Alex Belokrylov, Do not put all eggs in one container We will be happy to meet you at Oracle CodeOne conference in San Francisco, September 15 - 19. It is our honor to share the knowledge and…
- [Meet a BellSoft performance engineer at Devoxx Belgium | BellSoft Java](https://bell-sw.com/events/2019/10/03/Events-Devoxx-Belgium-2019/): Meet BellSoft performance engineer at Devoxx Belgium Java on ARM. Theory, Applications and Workloads, Dmitry Chuyko, Wednesday from 15:10 to 16:00 in Room 6 There are many great events for developers around the world.
- [Meet BellSoft engineers at JFokus in Stockholm | BellSoft Java](https://bell-sw.com/events/2020/02/02/Events-JFokus-Stockholm/): Meet BellSoft engineers at JFokus in Stockholm Check how small can be a Docker container with fully functional JDK at BellSoft Booth. Find us on February 4-5, 2020...
- [Get savvy about native image at JRush | BellSoft Java](https://bell-sw.com/events/2021/03/25/Events-JRush/): In the light of the Liberica NIK release, BellSoft invites high-level software developers and Java enthusiasts to discover native image. Join us on March 25 at 11 am PDT online. Learn how this wonderful tool can optimize your projects and minimize resources!
- [A web conference for DevOps engineers | BellSoft Java](https://bell-sw.com/events/2021/09/13/Events-JRush/): Dive into the latest DevOps trends and cutting-edge technologies. Find out how to increase the efficiency of your processes and accelerate the infrastructure transformation.
- [A web conference for senior engineers | BellSoft Java](https://bell-sw.com/events/2021/11/25/Events-JRush/): Evolve your Java apps with modern security and performance tools. Find out how to minimize migrations and upgrades and make your software flexible and ever-viable.
- [IANA Updater Update Time Zones information in Liberica JDK/JRE | BellSoft Java](https://bell-sw.com/iana-updater/): IANAUpdater is tool that can be used to update Time Zones information in Liberica JDK/JRE and, possilby, other OpenJDK-based distributions.
- [BellSoft. Secure Liberica JDK and modern solutions for Java apps | BellSoft Java](https://bell-sw.com/): BellSoft is a major OpenJDK contributor providing Progressive Java Runtime and other tools for Java apps for modern architectures and cloud.
- [The status of Java on Arm | BellSoft Java](https://bell-sw.com/java/arm/performance/2019/01/15/the-status-of-java-on-arm/): Today, as Arm processors are primarily viewed as targeting the embedded market, and justifiably so, multiple hardware vendors are using this architecture to build server CPUs and to compete with Intel in the cloud and High Performance Computing (HPC) segment.
- [A guide to cloud cost optimization | BellSoft Java](https://bell-sw.com/java-cloud-deployment-cost-optimization/): Discover a comprehensive strategy on reducing cloud expenses for good
- [Join the raffle and win a Steam Deck | BellSoft Java](https://bell-sw.com/java-forum-stuttgart/): Fill in the form to join a raffle for winning a PlayStation 5
- [Sales Representative – Key Accounts | Careers at BellSoft](https://bell-sw.com/jobs/sales-representative-key-accounts/): BellSoft is a major OpenJDK contributor providing Progressive Java Runtime and other tools for Java apps for modern architectures and cloud.
- [Sales Representative – Key Accounts (Japan) | Careers at BellSoft](https://bell-sw.com/jobs/sales-representative-key-accounts-japan/): BellSoft is a major OpenJDK contributor providing Progressive Java Runtime and other tools for Java apps for modern architectures and cloud.
- [Join the raffle and win a PlayStation 5 | BellSoft Java](https://bell-sw.com/kubecon/): Fill in the form to join a raffle for winning a PlayStation 5
- [Legal](https://bell-sw.com/legal-information/):
- [Liberica Administration Center | BellSoft Java](https://bell-sw.com/liberica-administration-center/): LAC controls all Java runtimes in a network of any size. Automate the updates, find the security issues and check your licenses in a single convenient tool.
- [Liberica Mission Control | BellSoft Java](https://bell-sw.com/liberica-mission-control/): Liberica Mission Control 7.1.1 is a low-overhead Java profiler built from OpenJDK JMC project.
- [Liberica Native Image Kit | BellSoft Java](https://bell-sw.com/liberica-native-image-kit/): Liberica Native Image Kit (Liberica NIK) is an open source tool for accelerating JVM software. It converts Java bytecode into an optimized native executable.
- [Liberica JDK End User License Agreement](https://bell-sw.com/liberica_eula/):
- [Liberica NIK End User License Agreement](https://bell-sw.com/liberica_nik_eula/):
- [Privacy Statement for BellSoft Software Subscriptions in Microsoft Azure](https://bell-sw.com/liberica_privacy_statement_azure/):
- [Liberica JDK | Java runtime from an OpenJDK contributor](https://bell-sw.com/libericajdk/): Liberica JDK is a free and open-source implementation of the Java SE platform for modern Java deployments supported by a leading OpenJDK contributor.
- [Liberica JDK container images | BellSoft Java](https://bell-sw.com/libericajdk-containers/): BellSoft provides ready Liberica JDK container images on Docker Hub, GHCR, and MCR.
- [Liberica JDK for Embedded | BellSoft Java](https://bell-sw.com/libericajdk-for-embedded/): Discover the value of a Liberica JDK build made specifically for embedded devices: secure, reliable, cost-efficient, and open source runtime with enhanced performance.
- [Liberica JDK Performance Edition | BellSoft Java](https://bell-sw.com/libericajdk-performance-edition/): Liberica JDK Performance Edition is a Java 11 runtime with all benefits of JDK 17: better startup time, throughput, latency of apps, and support with security patches.
- [Liberica JDK with CRaC support | BellSoft Java](https://bell-sw.com/libericajdk-with-crac/): Liberica JDK with CRaC support allows you to save the optimal running state of your app and later instantly launch it in that state.
- [BellSoft Media Kit | BellSoft Java](https://bell-sw.com/media-kit/): This is a media kit of BellSoft that can be distributed to members of the media.
- [BellSoft Expands Alpaquita Linux with AArch64 Support for Arm-Based Containers and Cloud Java Workloads](https://bell-sw.com/news/bellsoft-adds-aarch64-support-to-alpaquita-linux-distribution-and-alpaquita-containers/): BellSoft’s Alpaquita Linux now includes AArch64 support, optimizing Java on Arm-based hardware for the cloud. Experience faster response, lower costs, and secure containerized Java applications with this powerful, lightweight Linux distribution tailored for modern workloads.
- [BellSoft adds AArch64 support to Liberica JDK Performance Edition](https://bell-sw.com/news/bellsoft-adds-aarch64-support-to-liberica-jdk-performance-edition/): We are excited to announce the release of Liberica JDK Performance Edition for AArch64. This new edition integrates the powerful enhancements of JVM 17 into JDK 8 and 11, providing significant performance improvements for your Java workloads.
- [BellSoft Announces Hardened Builder for Paketo Buildpacks™, Bringing Zero-CVE Container Images to Buildpacks® Users](https://bell-sw.com/news/bellsoft-announces-hardened-builder-for-paketo-buildpacks-bringing-zero-cve-container-images-to-buildpacks-users/): San Jose, California (July 21, 2026) BellSoft, a leading OpenJDK vendor, announces the general availability of a new hardened builder image for Paketo Buildpacks™. Built entirely on BellSoft Hardened Images, the builder gives Paketo Buildpacks™, users a direct path to improved security and compliance posture, including continuous vulnerability management under SLA, signed images, and Software Bill of Materials, all inherited automatically with no changes to existing developer workflows.
- [BellSoft levels the performance of JDK 8 up to JVM 17 with Liberica JDK Performance Edition](https://bell-sw.com/news/bellsoft-levels-the-performance-of-jdk-8-up-to-jvm-17-with-liberica-jdk-performance-edition/): A widespread reason is the high financial and resource input required from companies for an upgrade to a newer LTS release. Meanwhile, the JVM in JDK 8 has lower performance, making your cloud use inefficient and therefore disappointing your customers.
- [BellSoft releases Alpaquita Containers for Spring Boot](https://bell-sw.com/news/bellsoft-releases-alpaquita-containers-for-spring-boot/): Alpaquita containers are based on Alpaquita Linux and Liberica JDK Lite integration and enhance your app with better speed, higher security, and improved overall performance.
- [BellSoft releases Alpaquita Containers with Coordinated Restore at Checkpoint support](https://bell-sw.com/news/bellsoft-releases-alpaquita-containers-with-coordinated-restore-at-checkpoint-support/): Java adaptation to the cloud environment comes with challenges, and the so-called "slow Java startup and warmup" is the one well-known. It can be described as a process that takes both time and memory for the traditional JVM to reach its exceptional peak performance in modern applications, resulting in higher costs and slower performance.
- [BellSoft releases dedicated builds of Liberica JDK 17 and 21 with CRaC](https://bell-sw.com/news/bellsoft-releases-dedicated-builds-of-liberica-jdk-17-and-21-with-crac/): Spring is one of the most popular Java frameworks, thanks to its deep tech prospects, pre-announced adding CRaC support. This is some of the most anticipated news for Java developers.
- [BellSoft releases Liberica JDK 21 for RISC-V with support](https://bell-sw.com/news/bellsoft-releases-liberica-jdk-21-for-risc-v-with-support/): RISC-V advantages are evident to many industries, from IoT to servers. Many companies in these industries use Java as their default programming language, and they are seeking to employ Java on RISC-V.
- [BellSoft's Hardened Images Set New Standard for Container Security](https://bell-sw.com/news/bellsoft-s-hardened-images-set-new-standard-for-container-security/): San Jose, California (November 10, 2025) BellSoft, the OpenJDK vendor delivering the most complete Java experience, announces Hardened Images, a tool for enhancing the security and compliance of containerized applications in Kubernetes. BellSoft will be demonstrating Hardened Images at KubeCon 2025 in Atlanta, beginning today.
- [BellSoft Upgrades Liberica JDK Performance Edition with JVM 21](https://bell-sw.com/news/bellsoft-upgrades-liberica-jdk-performance-edition-with-jvm-21/): BellSoft announces major upgrade to Liberica JDK Performance Edition, now powered by JVM 21. Delivers 5 to 10% better performance in most workloads, with gains of up to 40% in select cases.
- [Container Security Issues Continue to Befuddle Software Developers](https://bell-sw.com/news/container-security-issues-continue-to-befuddle-software-developers/): A new survey from BellSoft found that the tools and strategies developers are using to protect their companies from container-related incidents are undermining security goals
- [Spring Developers Have a Blindspot When It Comes to Container Security](https://bell-sw.com/news/spring-developers-have-a-blindspot-when-it-comes-to-container-security/): A survey from BellSoft found that Spring developers don’t know their Dockerfiles affect their security posture, aren’t using hardened images and can’t name their compliance framework, exposing their organizations, applications and users to considerable risk
- [The BellSoft News | BellSoft Java](https://bell-sw.com/newsroom/2/):
- [The BellSoft News | BellSoft Java](https://bell-sw.com/newsroom/3/):
- [The BellSoft News | BellSoft Java](https://bell-sw.com/newsroom/4/):
- [The BellSoft News | BellSoft Java](https://bell-sw.com/newsroom/):
- [The BellSoft News | BellSoft Java](https://bell-sw.com/newsroom/news-coverage/2/):
- [The BellSoft News | BellSoft Java](https://bell-sw.com/newsroom/news-coverage/3/):
- [The BellSoft News | BellSoft Java](https://bell-sw.com/newsroom/news-coverage/):
- [The BellSoft News | BellSoft Java](https://bell-sw.com/newsroom/press-releases/):
- [Technical Support for Alpaquita Linux | Bellsoft](https://bell-sw.com/nik-support/): With the BellSoft support team, you can focus on deploying infrastructure or services with the utmost level of security in your containerized project.
- [A comparative analysis of OpenJDK distributions | BellSoft Java](https://bell-sw.com/oracle-java-vs-open-jdk-distributions/): Compare key features of major OpenJDK distributions to find the best solution for your business
- [Extended Support for Java 6 and Java 7 | Security Updates, Patches, and Extended Roadmap](https://bell-sw.com/pages/6-7-java-support/): Keep your legacy Java 6 and 7 applications secure with BellSoft's Liberica JDK. Benefit from extended support, and timely security updates through March 2028. Learn more about how we help you.
- [OpenJDK Product Discovery API](https://bell-sw.com/pages/api/):
- [IANA Updater Update Time Zones information in Liberica JDK/JRE](https://bell-sw.com/pages/iana-updater/):
- [Liberica Native Image Kit Supported System Configurations | BellSoft Java](https://bell-sw.com/pages/nik-supported-configurations/): Discover Liberica Native Image Kit supported platforms. 100% open source tool for accelerating Java and JVM applications on Linux, Windows and macOS.
- [Package Managers for Liberica](https://bell-sw.com/pages/package-managers/):
- [BellSoft Liberica JDK Support | BellSoft Java](https://bell-sw.com/pages/support-login/): BellSoft provides High-Powered Support aimed to make the most of your OpenJDK investment. Rely on Java experts and take advantage of our safety guarantee.
- [BellSoft Liberica JDK Support | BellSoft Java](https://bell-sw.com/pages/support-partner/): BellSoft provides High-Powered Support aimed to make the most of your OpenJDK investment. Rely on Java experts and take advantage of our safety guarantee.
- [Supported System Configurations](https://bell-sw.com/pages/supported-configurations/):
- [BellSoft Partner Program – Grow with Us!](https://bell-sw.com/partner-program/): Join the BellSoft Partner Program and grow your business with industry-leading Java and cloud-native solutions. Whether you resell, refer, or implement, we provide the tools, resources, and support to help you succeed.
- [Privacy Policy](https://bell-sw.com/privacy-policy/):
- [A guide to enhancing Java apps security | BellSoft Java](https://bell-sw.com/security-of-java-applications/): Get a grip on recommendations and techniques for increasing the security of Java apps
- [Spring Container Security Report 2026 | BellSoft Survey](https://bell-sw.com/spring-container-security-2026/): 250 Spring developers surveyed at Spring I/O 2026. The findings reveal a security gap that surprised even us. Free report from BellSoft.
- [Join the raffle and win a Steam Deck | BellSoft Java](https://bell-sw.com/spring-io/): Fill in the form to join a raffle for winning a PlayStation 5
- [State of Container Security 2025 | Bellsoft Java](https://bell-sw.com/state-of-container-security-2025/): Free report: 427 professionals reveal container security challenges. Learn why 49% can't keep up with maintenance and what solutions teams are requesting. Download now.
- [Subscribe to our newsletter](https://bell-sw.com/subscribe/): Subscribe to our newsletter
- [Terms of Use](https://bell-sw.com/terms-of-use/):
- [Third-Party Licenses for BellSoft software](https://bell-sw.com/third_party_licenses/):
- [BellSoft. Unified Java runtime for TCO reduction | BellSoft Java](https://bell-sw.com/unified-java-runtime/): A Unified Java Runtime aims to cover all your Java needs with a wide variety of Java tools for most system configurations and any purpose.
- [5 Opinions on Distroless Images for Java Apps in 4 Minutes](https://bell-sw.com/videos/5-opinions-on-distroless-images-for-java-apps-in-4-minutes/): Distroless images, while smaller and aligned with modern immutable container practices, are not entirely "distroless" as they include a stripped-down Linux distribution, making them suitable for some, but not all, applications.
- [5 Tips for Optimizing Java Performance on Kubernetes](https://bell-sw.com/videos/5-tips-for-optimizing-java-performance-on-kubernetes/): If your Java apps in the cloud struggle with high resource consumption, frequent container restarts, or slow response times, these five tips can help enhance their performance. First, set CPU and RAM limits properly based on load testing and account for Kubernetes overhead.
- [5x Smaller Java Docker Images — 2025 Optimization Guide](https://bell-sw.com/videos/5x-smaller-java-docker-images-2025-optimization-guide/): In this video, I’ll show you how to make your Java Docker images 5 TIMES SMALLER. You’ll see the full process, from a heavy 587MB container to a lean 116MB image, ready for production.
- [All 7 Java Garbage Collectors Explained](https://bell-sw.com/videos/all-7-java-garbage-collectors-explained/): In this complete guide to Java garbage collection, you will learn how the JVM memory model works, understand the differences between the Serial, Parallel, G1, ZGC, Shenandoah, CMS, and Epsilon collectors, and determine which garbage collector is best suited for your application's performance — from single-threaded programs to massive terabyte-scale heaps.
- [An Overview of Java Garbage Collectors](https://bell-sw.com/videos/an-overview-of-java-garbage-collectors/): Java provides multiple garbage collectors (GCs) tailored to different performance needs. Serial GC is ideal for single-threaded apps but pauses all threads, while Parallel GC uses multiple threads to prioritize throughput.
- [Async Profiler: Uncover Hidden Performance Issues in Java!](https://bell-sw.com/videos/async-profiler-uncover-hidden-performance-issues-in-java/): Let's discuss Async Profiler, the small and effective open-source low-overhead profiler for Hotspot JVM-based application. Itcan collect data on various JVM events like CPU usage, heap allocation, methods, and so on. It can monitor non-Java threads, native calls, and kernel functions. It is small, silent, and incredibly effective!
- [Backend Developer Roadmap 2026: What You Need to Know](https://bell-sw.com/videos/backend-developer-roadmap-2026-what-you-need-to-know/): Backend complexity keeps growing, and frameworks can't keep up. In 2026, knowing React or Django isn't enough. You need fundamentals that hold up when systems break, traffic spikes, or your architecture gets rewritten for the third time.I've been building production systems for 15 years. This roadmap covers three areas that separate people who know frameworks from people who can actually architect backend systems: data, architecture, and infrastructure. This is about how to think, not what tools to install.
- [Best Oracle Java Alternatives in 2026 Comparison of OpenJDK Distributions](https://bell-sw.com/videos/best-oracle-java-alternatives-in-2026-comparison-of-openjdk-distributions/): A comparison of major OpenJDK distributions (Temurin, Liberica, Zulu, Corretto, Semeru, etc.), covering who maintains them, how updates are delivered, and what lifecycle guarantees they provide. We also explain why upstream OpenJDK isn’t production-ready and how your vendor choice impacts real-world systems. Useful for Spring Boot, containers, and Kubernetes to avoid hidden risks and choose the right runtime.
- [Boost The Performance and Security of Your Spring Boot App with Alpaquita Containers](https://bell-sw.com/videos/boost-the-performance-and-security-of-your-spring-boot-app-with-alpaquita-containers/): Alpaquita Containers offer a secure, high-performance solution for running Spring Boot applications in the cloud. These lightweight containers, built on Liberica JDK Lite and Alpaquita Linux, optimize memory and disk usage, reducing resource consumption by up to 30%.
- [Build RAG System with Spring AI: No More AI Lies](https://bell-sw.com/videos/build-rag-system-with-spring-ai-no-more-ai-lies/): Struggling to find answers in your own documentation? Your LLM is too. Hallucinations happen because models don’t know your data. RAG (Retrieval Augmented Generation) turns unstructured docs into searchable vector embeddings, so your LLM retrieves facts instead of inventing them. This tutorial walks through the full RAG workflow in Spring AI: document ingestion with TikaDocumentReader, embedding generation, vector storage (pgvector, Choma, Milvus, Oracle), and similarity-based retrieval. You’ll build two endpoints: one for uploading documents and one for answering questions strictly using your indexed data. When the system doesn’t know, it says so—no more confident nonsense. Designed for Java teams bringing AI into production systems where accuracy matters. You’ll learn the pattern, get the code, and deploy an LLM that finally stops hallucinating.
- [Build Typed AI Agents in Java with Embabel](https://bell-sw.com/videos/build-typed-ai-agents-in-java-with-embabel/): Most Java AI demos stop at prompt loops. That doesn't scale in production. In this video, we integrate Embabel into an existing Spring Boot application and build a multi-step, goal-driven agent for incident triage. Instead of manually orchestrating prompt → tool → prompt cycles, we define typed actions and let the agent plan across deterministic and LLM-powered steps. We parse structured input with Ollama, query MongoDB deterministically, classify risk using explicit thresholds, rank affected implants, generate a constrained root cause hypothesis, and produce a bounded containment plan. LLM handles reasoning. Java enforces rules. This is about controlled AI workflows on the JVM — not prompt glue code.
- [Buildpacks for Spring Boot](https://bell-sw.com/videos/buildpacks-for-spring-boot/): Buildpacks for Spring Boot: no Dockerfiles, no hassle — just production-ready container images in one command. Tired of maintaining Dockerfiles? In this tutorial, you’ll learn how to use buildpacks to create optimized Spring Boot containers — fast, secure, and cloud-ready — with just one command. We’ll show what happens under the hood: automatic dependency detection, layered image creation, memory tuning, SBOM generation, and how to tweak builds with just a few plugin options. Need faster startup, smaller image size, or JFR monitoring? Buildpacks can handle it — and we’ll show you how.
- [Docker Container Image Security: 13 Best Practices](https://bell-sw.com/videos/docker-container-image-security-13-best-practices/): This video presents 13 practical recommendations for reducing your attack surface and detecting malicious activity more quickly. You’ll learn how to create simple, immutable, and deterministic images using multi-stage builds, distroless bases, and non-root users. We cover SBOM generation with Syft, provenance verification with Cosign, CVE scanning workflows, and secret management strategies. From choosing LTS base images like Alpaquita Linux to implementing host-level protections, these practices will help you confidently deliver secure containers. It’s ideal for Java developers, DevOps engineers, and architects building production-grade infrastructure.
- [Dockerize Spring Boot Wisely: 6 tips to improve the container images of your Spring Boot apps](https://bell-sw.com/videos/dockerize-spring-boot-wisely-6-tips-to-improve-the-container-images-of-your-spring-boot-apps/): Your Spring Boot applications deserve a top-notch package!
- [Downgraded Java to JDK 1.1 After 30 Years… (part 1)](https://bell-sw.com/videos/downgraded-java-to-jdk-1-1-after-30-years-part-1/): How should we change Java 23 code for it to run on Java 1.1? We go line by line, removing modern features like records, sealed classes, switch expressions, var, and more. Each step reveals what breaks, how to rewrite it, and what you lose in the process. If you've ever wondered how far modern Java has drifted from its roots - this is your deep dive into that gap. This is Part 1 of the Java Downgrade Challenge, where we descend version by version until we reach Java 8. Subscribe to our channel to find out how we go even deeper - all the way down to Java 1.1. Stay tuned!
- [Dynamic SQL Queries with Spring Data JPA in 6 Minutes](https://bell-sw.com/videos/dynamic-sql-queries-with-spring-data-jpa-in-6-minutes/): If your repository layer has multiple queries for different filter combinations, your data access logic is already getting harder to maintain. In this video, we implement dynamic SQL queries in Spring Data JPA using Specifications — a composable approach that helps avoid query duplication and keeps your filtering logic clean. We build a flexible filtering system with optional parameters (category, language, format, price) and show how Specification.unrestricted() skips empty filters, while Specification.allOf(...) combines them into a single query. We also address a common issue: string-based field access. It’s fragile and can break at runtime when your model changes. Using the JPA Static Metamodel, we move to compile-time safety. The result is a cleaner, more maintainable way to implement dynamic filtering in Spring-based applications.
- [Flyway in Spring Boot: Step-by-Step tutorial with Maven](https://bell-sw.com/videos/flyway-in-spring-boot-step-by-step-tutorial-with-maven/): Learn how to use Flyway in Spring Boot with Maven for smooth and reliable database migrations. In this hands-on tutorial, we cover everything from setting up PostgreSQL in Docker, configuring Flyway in your application, writing versioned and repeatable migrations, to using Flyway in CI/CD pipelines with GitHub Actions. Whether you’re new to Flyway or want to master schema version control in Spring Boot, this video will guide you step by step.
- [GraalVM for Java Developers: The Ultimate Beginner’s Guide](https://bell-sw.com/videos/graalvm-for-java-developers-the-ultimate-beginner-s-guide/): What is GraalVM and how can it improve your Java applications? In just 10 minutes, this video explains the three main components of GraalVM — the JIT compiler, Native Image, and Polyglot API. Learn how to boost performance, reduce startup time, and combine multiple languages in one app. Whether you’re building microservices, serverless apps, or just exploring modern JVM tooling, this is your quick-start guide to GraalVM.
- [Hardened Container Images 101: What, Why, and How for DevSecOps [2025]](https://bell-sw.com/videos/hardened-container-images-101-what-why-and-how-for-devsecops-2025/): Why do production containers still ship with 600+ CVEs, package managers, and compilers? Most teams inherit bloated base images without ever checking what’s inside. Hardened container images fix this by removing unnecessary tools, enforcing immutability, and providing verifiable provenance. This tutorial breaks down the four pillars of hardened images—minimal base layers, low-to-zero CVEs, immutable runtimes, and SBOM-backed provenance—and shows how they compare to traditional and distroless images. You’ll also see practical examples: migrating from OpenJDK to hardened Liberica images with multi-stage builds, pinning images by digest, verifying signatures with Cosign, and integrating these checks into a production-ready CI/CD pipeline. Perfect for Java developers, DevOps engineers, and architects who want compliance-ready containers and dramatically fewer CVEs—without reinventing security every sprint.
- [Hibernate: Ditch or Double Down? When ORM Isn't Enough](https://bell-sw.com/videos/hibernate-ditch-or-double-down-when-orm-isn-t-enough/): Every Java team debates Hibernate at some point: productivity champion or performance liability? Both are right. This video shows you when to rely on Hibernate's ORM magic and when to drop down to SQL. We walk through production scenarios: domain models with many-to-many relations where Hibernate excels, analytical reports with window functions where JDBC dominates, and hybrid architectures that use both in the same Spring Boot codebase. You'll see real code examples: the N+1 query trap that kills performance, complex window functions and anti-joins that Hibernate can't handle, equals/hashCode pitfalls with lazy loading, and practical two-level caching strategies. We also explore how Hibernate works under the hood—translating HQL to database-specific SQL dialects, managing sessions and transactions through JDBC, implementing JPA specifications. The strategic insight: modern applications need both ORM convenience for transactional business logic and SQL precision for data-intensive analytics. Use Hibernate for CRUD and relationship management. Use SQL where ORM abstractions leak or performance demands direct control.
- [How to Create Dynamic SQL Queries with Spring Boot](https://bell-sw.com/videos/how-to-create-dynamic-sql-queries-with-spring-boot/): Build SQL queries dynamically based on the user input with the Specification interface. Use the JPA Static Model Generator to create type-safe queries. Run the tests to check your application.
- [How to Improve the Performance of Legacy Java Code](https://bell-sw.com/videos/how-to-improve-the-performance-of-legacy-java-code/): Your application is running on Java 8 or 11 and you can't migrate to a newer JDK ? Let's discuss the techniques that can speed up your Java app, cut memory usage, and reduce garbage collection pauses — and let you stay (for now) on the current JDK version.
- [How to install Liberica Native Image Kit on Windows PC](https://bell-sw.com/videos/how-to-install-liberica-native-image-kit-on-windows-pc/): Liberica Native Image Kit is a multilingual GraalVM-based set of utilities for creating native images. This guide will help you to install it on Windows PC.
- [How to Profile Java Applications in Docker Containers with JFR](https://bell-sw.com/videos/how-to-profile-java-applications-in-docker-containers-with-jfr/): Java applications in Docker containers using Java Flight Recorder (JFR), a built-in OpenJDK tool. It covers three profiling methods: enabling JFR at application startup, attaching to a running container using an ephemeral container with jcmd, and monitoring real-time performance with JDK Mission Control via remote JVM connections.
- [How to use AppCDS with Spring Boot](https://bell-sw.com/videos/how-to-use-appcds-with-spring-boot/): This tutorial demonstrates how to use Application Class Data Sharing (AppCDS) and Ahead-of-Time (AOT) processing with Spring Boot applications to reduce startup time by 40–50%. AppCDS creates an archive of parsed classes for faster loading, requiring no code changes, and works both locally and in containers. The tutorial covers building optimized Docker images using Dockerfiles or Buildpacks for efficient deployment and improved performance.
- [How to use CRaC with Spring Boot in a Docker Container](https://bell-sw.com/videos/how-to-use-crac-with-spring-boot-in-a-docker-container/): CRaC (Coordinated Restore at Checkpoint) is an OpenJDK project designed to significantly reduce startup and warmup times of Java applications to milliseconds. This tutorial demonstrates using CRaC with a Spring Boot application running in a Docker container, specifically the Spring Boot Petclinic app (version 3.2 or later).
- [I Solved Advent of Code 2025 in Kotlin: Here's How It Went](https://bell-sw.com/videos/i-solved-advent-of-code-2025-in-kotlin-here-s-how-it-went/): Every year, Advent of Code spawns thousands of solutions — but few engineers step back to see the bigger picture. This is a complete walkthrough of all 12 days from 2025, focused on engineering patterns rather than puzzle statements. We cover scalable techniques: interval math without brute force, dynamic programming, graph algorithms (JGraphT), geometry with Java AWT Polygon, and optimization problems that need constraint solvers like ojAlgo. You'll see how Java and Kotlin handle real constraints, how visualizations validate assumptions, and when to reach for libraries instead of writing everything from scratch. If you love puzzles, programming—or both—and maybe want to learn how to solve them on the JVM, this is for you.
- [Java 25 LTS: The New Features in JDK 25](https://bell-sw.com/videos/java-25-lts-the-new-features-in-jdk-25/): In this video, we go through every single JEP in JDK 25, explaining what it does, why it matters, and how it impacts real-world development. Whether you’re a Java veteran or just getting started, this breakdown will help you stay ahead of the curve.
- [Java 26 Preview: New JEPs and What They Mean for You](https://bell-sw.com/videos/java-26-preview-new-jeps-and-what-they-mean-for-you/): Java 26 is the next feature release that brings features for enhanced performance, security, and developer experience. This video discusses the upcoming JDK 26 release, highlighting ten JEPs including JEP 500. JEP 500 focuses on preparing developers for future restrictions on mutating final fields in Java, emphasizing their role in maintaining immutable state. This is crucial for robust programming and understanding the nuances of mutable vs immutable data, especially concerning an immutable class in java. We also touch upon the broader implications for functional programming in Java.
- [Java Developer Roadmap 2026: From Basics to Production](https://bell-sw.com/videos/java-developer-roadmap-2026-from-basics-to-production/): Most Java roadmaps teach tools. This one teaches order — the only thing that actually gets you to production. You don’t need to learn everything. You need to learn the right things, in the right sequence. In this video, we break down a practical Java developer roadmap for 2026 — from syntax and OOP to Spring, databases, testing, and deployment. Structured into 8 levels, it shows how real engineers grow from fundamentals to production-ready systems. We cover what to learn and what to ignore: core Java, collections, streams, build tools, Git, SQL and JDBC before Hibernate, the Spring ecosystem, testing with JUnit, and deployment with Docker and CI/CD. You’ll also understand why most developers get stuck — jumping into frameworks too early, skipping SQL, or treating tools as knowledge. This roadmap gives you a clear path into real-world Java development — with priorities, trade-offs, and production context.
- [Java Downgrade Challenge: From JDK 8 to 1.1 (Part 2)](https://bell-sw.com/videos/java-downgrade-challenge-from-jdk-8-to-1-1-part-2/): In Part 2 of the Java Downgrade Challenge, we continue our journey — now from Java 8 all the way to Java 1.1. No streams, no lambdas, no generics, no collections — and at one point, we even boot up Windows 98. If you thought Part 1 was painful, this one unwinds Java history line by line. By the end, the familiar Java from today will be almost gone.
- [Java DTO Guide: Fix Your API Design with One Simple Pattern](https://bell-sw.com/videos/java-dto-guide-fix-your-api-design-with-one-simple-pattern/): This tutorial shows how to use the Data Transfer Object (DTO) pattern to transfer data between application layers. We use Java records to reduce boilerplate code and the MapStruct library that simplifies Java bean mapping.
- [Java Flight Recorder Tutorial: How to Profile Java Applications](https://bell-sw.com/videos/java-flight-recorder-tutorial-how-to-profile-java-applications/): High CPU, GC spikes, or slow startup are common production issues, but logs and metrics don’t always reveal what the JVM is actually doing. Java Flight Recorder (JFR) provides a precise, low-overhead view of JVM behavior, safe for use even in production environments. In this video, you’ll learn how to use JFR to identify real bottlenecks such as CPU hotspots, memory allocation pressure, thread contention, and I/O stalls. We walk through the full workflow, including starting recordings with JVM flags, controlling them via jcmd, running JFR inside Docker containers, and attaching to live systems using ephemeral containers. Then we analyze a real Spring Boot recording in JDK Mission Control, breaking down GC behavior, allocation patterns, thread states, and method-level hotspots. If you want to move from symptoms to root cause with more confidence, this approach will help. Full article with commands and examples: [https://bell-sw.com/blog/how-to-profile-java-applications-with-jfr-beginner-s-guide/](https://bell-sw.com/blog/how-to-profile-java-applications-with-jfr-beginner-s-guide/)
- [Java in 2025: Busting the Biggest Myths](https://bell-sw.com/videos/java-in-2025-busting-the-biggest-myths/): Think Java is slow, outdated, or only for old-school enterprise apps? Think again. In this episode of Java Myth Busters, we debunk six common myths about Java, including performance issues, security concerns, and the myth that Java is dead. Discover how the JVM, Spring Boot, GraalVM, and modern JDK updates make Java one of the most powerful, scalable, and relevant languages in 2025.
- [Java in 2025: LTS Release, AI on JVM, Framework Modernization](https://bell-sw.com/videos/java-in-2025-lts-release-ai-on-jvm-framework-modernization/): Java in 2025 isn't about headline features, it's about how production systems changed under the hood. While release notes focus on individual JEPs, the real story is how the platform, frameworks, and tooling evolved to improve stability, performance, and long-term maintainability. In this video, we look at Java from a production perspective. What does Java 25 LTS mean for teams planning to upgrade? How are memory efficiency, startup time, and observability getting better? Why do changes like Scoped Values and AOT optimizations matter beyond benchmarks? We also cover the broader ecosystem: Spring Boot 4 and Framework 7, AI on the JVM with Spring AI and LangChain4j, Kotlin's growing role in backend systems, and tooling updates that make upgrades easier. Finally, we touch on container hardening and why runtime and supply-chain decisions matter just as much as language features.
- [Java Memory Options You Need in Production](https://bell-sw.com/videos/java-memory-options-you-need-in-production/): JVM memory tuning can be tricky. Teams increase -Xmx and assume the problem is solved. Then the app still hits OOM. Because maximum heap size is not the only thing that affects memory footprint. The JVM uses RAM for much more than heap: metaspace, thread stacks, JIT/code cache, direct buffers, and native allocations. That’s why your process can run out of memory while heap still looks “fine”. In this video, we break down how JVM memory actually works and how to control it with a minimal, production-safe set of flags. We cover heap sizing (-Xms, -Xmx), dynamic resizing, direct memory (-XX:MaxDirectMemorySize), and total RAM limits (-XX:MaxRAMPercentage) — especially in containerized environments like Docker and Kubernetes. We also explain GC choices such as G1, ZGC, and Shenandoah, when defaults are enough, and why GC logging (-Xlog:gc*) is mandatory before tuning. Finally, we show how to diagnose failures with heap dumps and OOM hooks. This is not about adding more flags. It’s about understanding what actually consumes memory — and making decisions you can justify in production.
- [JavaFX and scene Builder: Create UIs with a few clicks](https://bell-sw.com/videos/javafx-and-scene-builder-create-uis-with-a-few-clicks/): Creating user interfaces with JavaFX is easy! Simply install Scene Builder and create a layout with a few clicks! In this video we will create an interactive To Do app and in the process we will learn how to design UIs without coding.
- [JDBC Connection Pools in Microservices. Why They Break Down (and What to Do Instead)](https://bell-sw.com/videos/jdbc-connection-pools-in-microservices-why-they-break-down-and-what-to-do-instead/): In this livestream, Catherine is joined by Rogerio Robetti, the founder of Open J Proxy, to discuss why traditional JDBC connection pools break down when teams migrate to microservices, and what is a more efficient and reliable approach to organizing database access with microservice architecture.
- [JDBC vs ORM vs jOOQ: Choose the Right Java Database Tool](https://bell-sw.com/videos/jdbc-vs-orm-vs-jooq-choose-the-right-java-database-tool/): Still unsure what is the difference between JPA, Hibernate, JDBC, or jOOQ and when to use which? This video clarifies the entire Java database access stack with real, production-oriented examples. We start at the foundation, which is JDBC, a low-level API every other tool eventually relies on for database communication. Then, we go through the ORM concept, JPA as a specification of ORM, Hibernate as the implementation and extension of JPA, and Blaze Persistence as a powerful upgrade to JPA Criteria API. From there, we take a different path with jOOQ: a database-first, SQL-centric approach that provides type-safe queries and catches many SQL errors at compile time instead of runtime. You’ll see when raw JDBC makes sense for small, focused services, when Hibernate fits CRUD-heavy domains, and when jOOQ excels at complex reporting and analytics. We discuss real performance pitfalls such as N+1 queries and lazy loading, and show practical combination strategies like “JPA for CRUD, jOOQ for reports.” The goal is to equip you with clarity so that you can make informed architectural decisions based on domain complexity, query patterns, and long-term maintainability.
- [JDK 24: The New Features in Java 24](https://bell-sw.com/videos/jdk-24-the-new-features-in-java-24/): JDK 24 is in Rampdown Phase One, which means, we know all the JEPs targeted to this release. And there are a lot of them, so it is time to discuss this new Java release!
- [JEP 483: Ahead-of-Time Class Loading & Linking. Project Leyden in JDK 24](https://bell-sw.com/videos/jep-483-ahead-of-time-class-loading-linking-project-leyden-in-jdk-24/): JEP 483 introduces Ahead-of-Time (AOT) Class Loading and Linking in JDK 24, which enhances Java application startup times by loading and linking classes ahead of time and storing them in a reusable AOT cache. This feature, part of Project Leyden, reduces the JVM's workload during startup without requiring changes to application code, though a training run mimicking production is needed to create an efficient cache. Early tests with a Spring Boot app showed significant improvements, cutting startup time from two seconds to just one second.
- [jOOQ Deep Dive: CTE, MULTISET, and SQL Pipelines](https://bell-sw.com/videos/jooq-deep-dive-cte-multiset-and-sql-pipelines/): Some backend developers reach the point where the ORM stops being helpful. Complex joins, nested result graphs, or CTE pipelines quickly push frameworks like Hibernate to their limits. And when that happens, teams often end up writing fragile raw SQL strings or fighting performance issues like the classic N+1 query problem. In this video, we build a healthcare scheduling application NeonCare using jOOQ, Spring Boot 4, and PostgreSQL, and show how to write production-grade SQL directly in Java while keeping full compile-time type safety.
- [JRush | Container Essentials: Fast Builds, Secure Images, Zero Vulnerabilities](https://bell-sw.com/videos/jrush-container-essentials-fast-builds-secure-images-zero-vulnerabilities/): Web-conference for Java developers focused on hands-on strategies for building high-performance containers, eliminating CVEs, and detecting security issues before production.
- [JRush Ep 5 - Advanced Security Strategies for Java and Spring Applications](https://bell-sw.com/videos/jrush-ep-5-advanced-security-strategies-for-java-and-spring-applications/): Web-conference for engineers focused on real-world solutions for enhancing Java security, managing AI threats, and learning about zero-day responses.
- [JRush Episode 4: Q&A Session](https://bell-sw.com/videos/jrush-episode-4-q-a-session/): JRush Episode 4 “Modern Java development for Banking and FinTech”, we had a very fruitful discussion with our speakers, Dr Mo Haghighi, Mary Grygleski, and Dmitry Chuyko
- [Jrush episode 4th: Build your Cloud Native Application with Kubernetes](https://bell-sw.com/videos/jrush-episode-4th-build-your-cloud-native-application-with-kubernetes/): Learn how to build your Cloud Native Application with Dr Mo Haghighi
- [JRush episode 4th: Event streaming with Apache Pulsar](https://bell-sw.com/videos/jrush-episode-4th-event-streaming-with-apache-pulsar/): Discover how to perform efficient and reliable event streaming with Apache Pulsar with Mary Grygleski, Streaming Developer Advocate at DataStax
- [JRush episode 4th: Fresh Java on modern Arm servers](https://bell-sw.com/videos/jrush-episode-4th-fresh-java-on-modern-arm-servers/): Explore the world of modern Arm servers with Dmitry Chuyko, Senior Performance Architect at BellSoft.
- [Master Java Profiling in 2025: Tools, Techniques, and Real-World Tips](https://bell-sw.com/videos/master-java-profiling-in-2025-tools-techniques-and-real-world-tips/): In this complete guide to Java profiling, you will learn sampling and instrumentation techniques, compare the 7 best tools (JFR, VisualVM, Async Profiler, JProfiler, YourKit, Digma.ai, New Relic), and master how to detect memory leaks and analyze CPU usage.
- [Master Java Profiling: Tools, Techniques, and Real-World Tips](https://bell-sw.com/videos/master-java-profiling-tools-techniques-and-real-world-tips/): Java profiling allows to rapidly identify and fix performance bottlenecks in your program. In this video we explain what is profiling, introduce popular profiling tools, list their pros and cons, and provide useful tips and code examples.
- [OpenJDK projects that we anticipate](https://bell-sw.com/videos/openjdk-projects-that-we-anticipate/): OpenJDK is actively evolving, with projects like Leyden, Valhalla, Babylon, and Lilliput aiming to enhance Java's performance and capabilities. Leyden focuses on faster startup and warmup by reusing precompiled code, while Valhalla introduces value objects, primitive classes, and specialized generics for better memory and runtime efficiency.
- [Advanced Kotlin Techniques for Spring Developers](https://bell-sw.com/videos/pasha-finkelshteyn-at-spring-i-o-2024-advanced-kotlin-techniques-for-spring-developers/): As a seasoned developer, you're likely already familiar with Spring. But Kotlin can take your developer experience with Spring to the next level! Join this session and learn how to add new functionality to existing classes with Kotlin extension functions, use Kotlin bean definition DSL, improve third-party libraries with varargs, and leverage coroutines with Spring idiomatically. By the end of this talk, you'll have a deeper understanding of the advanced Kotlin techniques that are available to you as a Spring developer and be able to use them effectively in your projects.
- [PF4J: Plugin Framework for Java. Plugin Systems for Backend](https://bell-sw.com/videos/pf4j-plugin-framework-for-java-plugin-systems-for-backend/): PF4J (Plugin Framework for Java) is a lightweight framework that allows developers to create modular applications using plugins. It enables the integration of custom code into applications through extension points, with support for lifecycle management and Spring integration for dependency injection. PF4J is useful for both desktop and web applications, offering flexibility in scaling and extending functionality without altering the core system.
- [Reducing Java Startup Time: 4 Approaches](https://bell-sw.com/videos/reducing-java-startup-time-4-approaches/): Java application startup can be significantly accelerated using modern tools. AppCDS stores preloaded classes in a shared archive, cutting startup time by up to 50%, while Project Leyden shifts optimizations to earlier stages with ahead-of-time compilation. GraalVM Native Image creates standalone executables for sub-second startup, and CRaC restores pre-warmed application states for instant readiness.
- [SBOMs & Java Security: Stay Compliant, Stay Protected!](https://bell-sw.com/videos/sboms-java-security-stay-compliant-stay-protected/): A software bill of materials or SBOM is crucial for the modern development environment full of regulations, vulnerabilities, and hidden threats. In this video, we discuss what SBOMs are and how they help dealing with compliance audits and application security. We will explore the best open-source tools for generating SBOMs and provide a tutorial on creating an SBOM for your project and analyzing it for vulnerabilities.
- [Should You Still Use LOMBOK in 2025?](https://bell-sw.com/videos/should-you-still-use-lombok-in-2025/): Have you ever wondered how @Data, @Builder, and @Getter actually work? Lombok doesn’t just generate code — it rewrites your AST during compilation using internal, unofficial APIs. And while many developers enjoy the reduced boilerplate, few realize the hidden risks behind it.
- [Sizing JDBC Connection Pools for Real Production Load](https://bell-sw.com/videos/sizing-jdbc-connection-pools-for-real-production-load/): Many production outages start with connection pool exhaustion. Your app waits seconds for connections while queries take milliseconds; yet, most teams run default settings that collapse under load. This video shows how to configure connection pools that survive real production traffic: sizing based on database limits and thread counts, setting timeouts that prevent cascading failures, and implementing an open source database proxy Open J Proxy for centralized connection management with virtual connection handles, client-side load balancing, and slow query segregation. For senior Java developers, DevOps engineers, and architects who need database performance that holds under pressure.
- [Spring AI: Streaming LLM Tokens with NDJSON in Spring Boot](https://bell-sw.com/videos/spring-ai-streaming-llm-tokens-with-ndjson-in-spring-boot/): Streaming LLM responses smoothly is harder than it looks. SSE often breaks LLM token spacing and words merge. This video shows how to fix it using Spring AI, WebFlux, and NDJSON. You’ll learn why Server-Sent Events can fail with modern LLM tokenizers, how NDJSON provides reliable token streaming, and how to build a production-ready pipeline with proper error handling and backpressure control. We cover configuring Spring AI ChatClient with Ollama, creating a reactive NDJSON endpoint, handling errors with onErrorResume, managing backpressure with limitRate, and consuming the NDJSON stream on a Vaadin frontend using WebClient. The result: stable, clean LLM streaming. Tested with Spring Boot 3.5.7, Java 25 (Liberica JDK), and Ollama both locally and in Docker.
- [Spring Boot Buildpacks: Optimize, Secure, and Shrink Your Containers!](https://bell-sw.com/videos/spring-boot-buildpacks-optimize-secure-and-shrink-your-containers/): Buildpacks let you containerize Spring Boot apps without writing Dockerfiles, but the default settings aren’t always optimal. In this video, I’ll show you how to fine-tune buildpacks for faster startup, smaller images, and better security—so you get the best performance with minimal effort.
- [Spring Data MongoDB: From Repositories to Aggregations](https://bell-sw.com/videos/spring-data-mongodb-from-repositories-to-aggregations/): Spring Data MongoDB breaks down fast once CRUD meets production—real queries, actual data volumes, analytics. What looks simple at first quickly turns into unreadable repository methods, overfetching, and slow queries. In this video, I walk through building a production-style Spring Boot application using Spring Data MongoDB — starting with basic setup and repositories, then moving into indexing, projections, custom queries, and aggregation pipelines. You'll see how MongoDB's document model changes data design compared to SQL, when embedding helps, and when it becomes a liability. We cover where repository method naming stops scaling, how to use @Query safely, when to switch to MongoTemplate, and how to reduce payload size with projections and DTOs. Finally, we implement real MongoDB aggregations to calculate analytics directly in the database and test everything against a real MongoDB instance using Testcontainers. This is not another MongoDB overview. It's a practical guide to actually using Spring Data MongoDB in production without fighting the database.
- [Spring Developer Roadmap 2026: What You Need to Know](https://bell-sw.com/videos/spring-developer-roadmap-2026-what-you-need-to-know/): Spring Boot is powerful. But knowing the framework isn’t the same as understanding backend engineering. In this video, I walk through the roadmap I believe matters for a Spring developer in 2026. We start with data. That means real SQL — CTEs, window functions, normalization trade-offs — and understanding what ACID and BASE actually imply for system guarantees. Spring Data JPA is useful, but you still need to know what happens underneath. Then architecture: microservices vs modular monolith, serverless, CQRS, and when HTTP, gRPC, Kafka, or WebSockets make sense. Not as buzzwords — but as design choices with trade-offs. Security and infrastructure follow: OWASP Top 10, AuthN vs AuthZ, encryption in transit and at rest, Docker, Kubernetes, Infrastructure as Code, and observability with Micrometer, OpenTelemetry, and Grafana. This roadmap isn’t about mastering every tool. It’s about knowing what affects reliability in production.
- [Stop Using DTOs – A Cleaner Way for Your Java APIs](https://bell-sw.com/videos/stop-using-dtos-a-cleaner-way-for-your-java-apis/): Still creating DTOs for every API in your Spring Boot project? You might be overcomplicating things. In this video, we show why DTOs aren’t always necessary and how to replace them with @JsonIgnore, @JsonView, and Jackson Mixins. You’ll see real examples of hiding sensitive fields, creating role-based views, and cutting boilerplate — all while keeping your API safe, clean, and easy to maintain.
- [Swagger UI Integration in Spring Boot 3](https://bell-sw.com/videos/swagger-ui-integration-in-spring-boot-3/): How to integrate Swagger UI in Spring Boot 3 using springdoc-openapi This beginner-friendly tutorial shows how to document REST APIs in Java using Swagger UI, customize OpenAPI metadata, group and version endpoints, and secure the Swagger interface — all with Spring Boot 3 and Java 21.
- [TOP-5 Lightweight Linux Distributions for Containers](https://bell-sw.com/videos/top-5-lightweight-linux-distributions-for-containers/): In this video, we compare five lightweight Linux distributions commonly used as base images: Alpine, Alpaquita, Chiseled Ubuntu, RHEL UBI Micro, and Wolfi. There are no rankings or recommendations — just a structured look at how these distros differ so you can evaluate them in your own context.
- [Top 7 JavaFX Testing Mistakes You Need To Avoid!](https://bell-sw.com/videos/top-7-javafx-testing-mistakes-you-need-to-avoid/): Stop making these common JavaFX testing mistakes! No more random NullPointerExceptions or deadlocks — in this video, we’ll show you how to fix the 7 most frequent TestFX issues when testing JavaFX applications. Learn how to work with FX threads, integrate with Spring Boot, avoid event-queue race conditions, handle pixel-level test differences, set up headless continuous integration with Monocle, and properly separate business logic from UI tests.
- [Spring Boot to Native Image in 1 Command — GraalVM Tutorial](https://bell-sw.com/videos/turn-your-4-second-java-startup-into-0-5-seconds-with-graalvm-native-image/): This video covers theory and hands-on practice: how Native Image works, distribution comparison (Liberica NIK, Oracle GraalVM, Mandrel), and real Spring Boot applications including an AI chatbot and Vaadin frontend. From Maven plugins to Docker deployment and tracing agent troubleshooting.
- [Vaadin Tutorial: From Spring Boot to Beautiful UI Fast](https://bell-sw.com/videos/vaadin-tutorial-from-spring-boot-to-beautiful-ui-fast/): In this guide, I’ll show you how to build a fully functional Java application with authentication, data tables, filters, and a custom cyberpunk theme using Vaadin.
- [Why Test Performance When Scaling K8s](https://bell-sw.com/videos/why-test-performance-when-scaling-k8s/): Running the HotSpot JVM in containers requires careful tuning of resource limits, garbage collection, and JVM options to maximize performance and avoid issues like container restarts or poor scaling.
- [Will AI Replace Developers? A Vibe Coding Reality Check 2025](https://bell-sw.com/videos/will-ai-replace-developers-a-vibe-coding-reality-check-2025/): Can AI replace software engineers? ChatGPT, Copilot, and LLM-powered vibe coding tools promise to automate development—but after testing them against 17 years of production experience, the answer is more nuanced than the hype suggests. Full project generation produces over-engineered code that's hard to refactor. AI assistants excel at boilerplate but fail at business logic. MCP servers solve hallucination problems but create context overload. Meanwhile, DevOps automation actually works. This breakdown separates AI capabilities from marketing promises—essential for teams integrating LLMs and copilots without compromising code quality or architectural decisions.
- [Liberica JDK | Java runtime from an OpenJDK contributor](https://bell-sw.com/vulnerability-report/): Liberica JDK is a free and open-source implementation of the Java SE platform for modern Java deployments supported by a leading OpenJDK contributor.
- [It's Fine Actually: Doing Better in Legacy Java](https://bell-sw.com/webinars/java8-fine-actually/): Still on Java 8 or 11? No judgment, most of us are. Maybe the rewrite isn't coming. Maybe the upgrade to Java 17 or 21 is always six months away. Maybe that old system just… works. This webinar is about what to do when you're not moving on. Because staying doesn't have to mean suffering, and it definitely doesn't mean giving up on good engineering.
- [Mastering Kubernetes: Best Practices for Deploying Spring Boot Applications | Java runtime from an OpenJDK contributor](https://bell-sw.com/webinars/mastering-kubernetes-best-practices/):
- [Reactive Web Apps With 100% Java With Vaadin](https://bell-sw.com/webinars/reactive-web-apps-with-vaadin/): Full-stack development can be intimidating, especially when going beyond the typical CRUD. A reactive application with real-time updates requires knowing things like WebSockets on top of the regular frontend stuff. But what if you could build a full-featured full-stack web app using only straightforward Java? No JavaScript, no HTML, no CSS. Just Java. Join this live coding session to discover how to develop a reactive web app using Spring Boot and the open-source Vaadin framework. Starting from scratch, we'll cover everything: real-time updates, external services, and a good-looking UI. All in Java.
- [掌握 Kubernetes: 部署 Spring Boot 应用程序的最佳实操 | Java runtime from an OpenJDK contributor](https://bell-sw.com/zh-cn/webinars/mastering-kubernetes-best-practices/):
## Downloads
- [Download Alpaquita Linux | BellSoft Java](https://bell-sw.com/pages/downloads/alpaquita/): Download Alpaquita Linux, a free and open-source Linux distro tailored to Java apps and microservices.
- [Access Alpaquita Linux cloud images for AWS | BellSoft](https://bell-sw.com/pages/downloads/alpaquita-cloud-image/): Access Alpaquita Linux, the Alpine-based build fine-tuned for Java with glibc and musl support.
- [Access Alpaquita Linux cloud images for AWS | BellSoft](https://bell-sw.com/pages/downloads/alpaquita-gcp/): Access Alpaquita Linux, the Alpine-based build fine-tuned for Java with glibc and musl support.
- [Download JDK 8, 11, 17, 21, 25, 26 | Java Builds for Linux, Windows and macOS](https://bell-sw.com/pages/downloads/): Download Liberica JDK, supported OpenJDK builds. Open source Java 8, 11 and more for Linux, Windows, macOS.
- [Download Liberica Native Image Kit, NIK 23 (JDK 17), NIK 23 (JDK 21), NIK 25 (JDK 25), Linux, macOS | BellSoft Java](https://bell-sw.com/pages/downloads/native-image-kit/): Download Liberica Native Image Kit for Linux, Windows, macOS. A tool to boost Java and JVM-based applications. Built from open source GraalVM CE and OpenJDK.
## Blog
- [The status of Java on Arm](https://bell-sw.com/java/arm/performance/2019/01/15/the-status-of-java-on-arm/): Today, as Arm processors are primarily viewed as targeting the embedded market, and justifiably so, multiple hardware vendors are using this architecture to build server CPUs and to compete with Intel in the cloud and High Performance Computing (HPC) segment. This broadens the variety of Java applications that run on Arm CPUs and adds to the complexity of the Java Arm port itself, as it must support a segmented variety of CPU vendors and workloads. In this post which follows the article written for the Java Magazine, I explore the status of Java and the Java ecosystem on Arm and its evolution. I also discuss some recent developments in Java Arm port features and performance, emphasizing both the server and IoT/embedded deployments. Why Arm? Leaving aside the embedded and mobile markets, where Arm dominates with 32-bit ARMv5, 6, 7 and 8 ISA, it’s no longer stretching the point to say that Arm provides a viable alternative to the markets that are currently dominated by x86 architecture. Unlike CPU vendors like Intel, which focus on shipping processors and evolve the x86 architecture to do so, Arm is primarily an architecture design company selling architectural and core licenses to its customers, which turn it into actual silicon. This allows a great variety of actual implementations of the same architecture to co-exist and compete in different market segments. It is clearly visible from the recent developments of the Arm architecture itself that the focus has shifted to allow competitive Arm-based server CPU designs. In 2016, Arm finalized a 64-bit and 32-bit capable ARMv8-A ISA, targeting both the embedded and server markets. ARMv8-A architecture, which added support for 64-bit and mandated the presence of NEON SIMD instructions, also introduced optional instructions for AES encryption, SHA-1, SHA-256 and CRC32, which some vendors implement to boost cryptographic and checksum performance. Arm did not stop there. In 2017, Arm extended this architecture with the ARMv8.1-A update, most notably adding new atomic instructions. Later, ARMv8.2-A added half-precision floating-point data processing and dot product SIMD instructions. What’s more important, starting with ARMv8.2-A, optional SVE (Scalable Vector Extension) instructions introduced better support for vectorization compared to the NEON instruction set, making the ARMv8 architecture much better suitable for HPC. Recently, ARMv8.3-A added SIMD complex number support and weaker release consistency instructions. The ARMv8 architecture leaves room for vendor design selection to achieve performance, complexity, and power goals. It adopts a relaxed hardware memory model which is weaker than x86-TSO. Thus, one can observe more out-of-order effects. Compared to ARMv7, there are useful concurrency primitives, including the load-acquire and store-release instructions, as well as weaker barrier instructions. But a cautious programmer who follows Java language memory model will not notice these differences, because the JVM hides it inside the implementation. Several hardware vendors contend with Intel in the server market with their ARMv8-based processor designs, and this should already be taken seriously. Some hardware vendors are new to the Arm server ecosystem, such as Qualcomm with its Centriq 2400 offering, or the relaunched Ampere, which possesses assets from APM. Others, like Cavium, which was recently acquired by Marvell, are already established in the Arm server market and have a track record of delivering ThunderX production systems for several years now. They recently released the second generation ThunderX2 systems. ThunderX systems are readily available in the cloud from cloud providers such as Packet and Amazon. The real competition for ARMv8-based server hardware vendors is not the ARMv8 vendors themselves. It is Intel who they all are trying to compete with. And there is more than just the cloud market for Arm: Cray and HPE are shipping HPC solutions based on ARMv8 architecture, making the HPC future of ARMv8 real. As part of the Vanguard program, Sandia National Labs is deploying its Arm-based supercomputer with a theoretical peak of more than 2.3 petaflops. Aside from the CPU core design, ARMv8-based server vendors invest heavily in parallelism and memory bandwidth, while keeping the power consumption low. For example, Qualcomm Centriq 2400 platform is said to have 48 single-threaded cores and 6 channels of DDR4 memory per SoC. Marvell ThunderX2 has up to 64 four-threaded cores in dual-socket configuration (making the total number of hardware threads 256) and 8 channels of DDR4 memory per socket. In the embedded segment, things are more traditional, and most chip makers license the Cortex-A core from Arm instead of building their own. All major Linux distributions support Arm, including Debian, Oracle, Red Hat, SuSE, Ubuntu. All the tooling at the OS and kernel level is already there, stable and ready for production use. Availability of Java on Arm End users will find a good choice of providers of Java and OpenJDK binaries for Arm. Both the ARMv7 and ARMv8 Java ports are fully functional and the sources are available from OpenJDK under the GPLv2.1 license with the classpath extension, which enabled most Linux distributions to include them in their package repository. Sometimes using OpenJDK binaries provided by the Linux package management systems is for some reason not preferred. For instance, if your favorite Linux distribution does not contain the required packages or you are looking for commercial support, there is an excellent choice of different versions of Java/OpenJDK binaries provided by AdoptOpenJDK, Azul, BellSoft and Oracle. At the time this article was published, Oracle only provided JDK 8 binaries for ARMv8 and ARMv6/7, Azul provided binaries for JDK 8 and 11, while BellSoft offers binaries for JDK 8, 9,10 and 11 which, for the Raspberry Pi, include the OpenJFX and Device IO API modules. Azul, BellSoft and most notably Oracle provide supported binaries that comply with the Java SE specification and verify their binaries with the JCK test suite. Speaking of “your favorite Linux distribution.” My team and I advocate for OpenJDK adoption on Alpine Linux as one of the most cost-effective choices you can make for your project. Error-free work of the musl library in Java apps (without a glibc layer) had been a major BellSoft’s project. The release of JDK 16 introduced JEP 386 that welcomed full-fledged support for Alpine Linux musl in OpenJDK. But what about other common Java SE implementations? In July 2021, when this article was updated, Alpine Linux support in the Java industry could be described with Table 1. Table 1. Availability of Alpine Linux musl Java binaries across OpenJDK distributors. Alpine Linux – Java Liberica JDK Oracle JDK AdoptOpenJDK Red Hat build Corretto Zulu 8 11 16 8 11 16 8 11 16 8 11 16 8 11 16 8 11 16 Alpine Linux/x86 ✓ ✓ ✓ – – – – – ✓ – – – ✓ ✓ ✓ ✓ ✓ ✓ Alpine Linux/AArch64 ✓ ✓ ✓ – – – – – ✓ – – – – – – – – – BellSoft is the only developer that provides support for Alpine Linux + Java on AArch64. These are verified builds, production-ready and available for LTS versions (Java 8 and 11). Features of the Java Arm ports Java and OpenJDK Arm ports are mature for production use. The minimum requirement for Java and OpenJDK implementations is seeking conformance with the Java SE Specification by passing the Java SE Compatibility Test Suite (JCK). Arm and ARMv8 ports have reached that level of compatibility long ago and are first-class citizens among Java-supported platforms, along with x86 and SPARC. While it is very important to ensure compatibility of Java implementations, passing the JCK is not the only requirement for a successful Java port. To meet startup and throughput performance expectations, both ARMv7 and ARMv8 ports have C1 and C2 JIT compilers implemented, thus allowing them to produce optimized code that takes advantage of the underlying architecture specifics. On top of that, the -XX:+TieredCompilation is supported and turned on in the Server VM, which allows for leveraging from fast startup and achieving C2 throughput. A full set of GCs is supported in both ARMv7 and ARMv8 ports: ParallelGC, G1, SerialGC, CMS (Deprecated). For embedded use-cases, the ARMv7 port seen in some bundles also carries a lightweight Minimal VM. For JDK 9 or higher, it allows building Java runtime images with a low static footprint using the Jigsaw feature. For example, running OUTPUT=~/out bin/jlink --module-path jmods --compress=2 --add-modules java.base --output $OUTPUT rm -r $OUTPUT/lib/client $OUTPUT/lib/server echo "-minimal KNOWN" > $OUTPUT/lib/jvm.cfg on BellSoft Arm JDK 10, which provides Minimal VM, produces a java runtime with the java.base module with a static footprint as small as 16 Mb. Surprisingly, java.base (maybe with the addition of several other modules) is sufficient for quite a number of Java applications targeted for constrained IoT gateways. For example, a runtime capable of running Apache Felix or Jetty fits into 32 Mb. Over the years, the ARMv8 port received built-in optimized assembly intrinsics for CPU-intensive operations. At present, only several intrinsics present in the x86 port are absent in the ARMv8 port, and the gap is rapidly closing. All the common features which appear in other ports work on Arm, and several specific to the JVM, such as Docker support, AppCDS v2 were ensured to work on Arm as well. See Table 2 for a detailed comparison of major JVM features across upstream x86, ARMv8 64bit and 32bit Arm ports. Table 2. Comparison of major upstream x86 and Arm JVM port features. x86/64 AARCH64 ARM (32-bit) VMs Client Yes No Yes Server Yes Yes Yes Minimal Yes (32 bit) Yes, since JDK 12 Yes JIT C1 Yes Yes Yes C2 Yes Yes Yes TieredCompilation Yes Yes Yes Graal JIT (Experimental) Yes, since JDK 10 Yes, since JDK 11 No GC SerialGC Yes Yes Yes ParallelGC Yes Yes Yes CMS Yes, Deprecated Yes, Deprecated Yes, Deprecated G1 Yes Yes Yes ZGC Experimental In development No Runtime Container support Yes Yes Yes AppCDS Yes Yes, since JDK 10 Yes, since JDK 10 HugePages Yes Yes Yes Numa Support Yes Yes No Serviceability Java Flight Recorder Yes Yes, since JDK 11 Yes, since JDK 11 Performance of AARCH64 JVM port Hardware, OS and the JVM all contribute to the performance of Java applications and benchmarks. Let's dive into the performance of the ARMv8 port, as the server market is where performance matters most. To make a good comparison, it is important to find x86 and Arm server equivalents. Luckily, the recently released Marvell ThunderX2 ARMv8 CPUs provides a comparable Intel equivalent for each SKU based on SPECint2017 rates. From this table, for studying performance, I selected the ThunderX2 CN9975 and its comparable Intel Xeon Gold 6140 single socket system, both equipped with DDR4-2666 memory and running Ubuntu 16.04. Dual socket systems with these CPUs are also available. It’s interesting to note that ThunderX2 CN9975 CPU has 112 threads (28-core system with 4-way SMP), and the comparable Intel Xeon Gold 6140 has 36 threads (18-core system with Hyper-Threading). To assess the performance of the JVM ARMv8 and x86 ports, I used the SPECjbb2015 1.01 and SPECjvm2008 1.01 benchmarks running the OpenJDK 11 EA build 18. All benchmarks were executed 20 times, and mean values were collected. The SPECjbb2015 benchmark was used to obtain an overall score, while the SPECjvm2008 provided additional insights into the performance of ARMv8 HotSpot JVM port. Since this article is not intended to report the best score obtainable on a specific hardware system, but to study the performance of what a typical end user would see, I intentionally did not fine-tune low level JVM parameters or kernel settings on either system. Check the SPEC scores for the processors as reported by the hardware vendors to compare the highest achievable numbers with JVM options tuning. SPECjbb2015 results The SPECjbb2015 1.01 Composite results (Critical-jOPS and Max-jOPS) are presented in Figure 1. The JVM command line options used for these runs were very common for SPECjbb2015 runs: -Xmx24G -Xms24G -Xmn16G -XX:+AlwaysPreTouch -XX:+UseParallelGC -XX:+UseTransparentHugePages -XX:-UseBiasedLocking for ARMv8 and -Xmx24G -Xms24G -Xmn16G -XX:+AlwaysPreTouch -XX:+UseParallelGC -XX:+UseTransparentHugePages -XX:+UseBiasedLocking for x86. Switching biased locking off for ARMv8 and leaving it on for x86 gave both platforms slightly better results. Figure 1. SPECjbb2015-Composite performance results on single-socket Xeon Gold 6140 and ThunderX2 CN9975 with DDR4-2666 memory running Ubuntu 16.04. Higher is better. As can be seen from the results presented, OpenJDK 11 ARMv8 port running on ThunderX2 outperforms the x86 port on Xeon Gold 6140 by 33% in SPECjbb2015 Max-jOPS score and by 16% in SPECjbb2015 Critical-jOPS score. Long story short, the ThunderX2 system with ARMv8 JVM port is very well suitable for enterprise workloads represented by the SPECjbb2015 benchmark. To assess the per-thread performance, I also limited the number of CPU threads on ThunderX2 to be the same as on Intel Xeon Gold 6140, which only used 32% of its CPU threads. Unsurprisingly, in this case SPECjbb2015 clearly favoured the Xeon Gold, giving it a 30% advantage. SPECjvm2008 results The SPECjvm2008 Base results for individual benchmarks together with the composite Base are presented in Figure 2. Since the SPECjvm2008 “compiler” benchmark has not worked since JDK 8, the composite geomean base score was manually calculated without a “compiler” benchmark result. Figure 2. SPECjvm2008 performance results on single-socket Xeon Gold 6140 and ThunderX2 CN9975 with DDR4-2666 memory running Ubuntu 16.04. Higher is better. As can be seen from the results presented, the OpenJDK 11 ARMv8 port running on ThunderX2 outperforms the x86 port on Xeon Gold 6140 by 28% in SPECjvm2008 benchmark composite Base score. There are two main reasons for the overall better ARMv8 system score. The first is that it has a higher memory bandwidth (8 channels compared to 6 channels on Intel). The second is related to the work done in the ARMv8 Java port that allowed for the full utilization of the CPU potential and extensions. To gain additional insights, let's explore the scores for individual SPECjvm2008 workloads. In eight out of nine SPECjvm2008 benchmarks, the ARMv8 port outperformed x86, and for one the results were the opposite. The Crypto benchmark clearly favors an ARMv8-based system, giving it a 62% advantage, which would not be reachable if the ARMv8 port didn’t fully utilize the AES and SHA extensions available on this chip. The compress benchmark (where ARMv8-based system beats Intel by 12%) uses the CRC32C intrinsic. XML (ARMv8 beats Intel by 29%) and MpegAudio (by 44%) benchmarks use the java.lang.String and java.lang.Arrays intrinsics. Some of these intrinsics were recently improved in JDK 10 and in 11 for ARMv8 by BellSoft together with Cavium/Marvell. It is also important to understand the results for the benchmark where x86 OpenJDK port did better: scimark.small (by 29%). The reason for that is the benchmark code: FFT, LU, SOR and SPARSE scimark subbenchmarks all contain heavy loops and matrix computation code. Over the years a lot of efforts have been made by Intel into loop unrolling and vectorization, which allowed for mapping such code sequences to AVX instructions on x86. This work has not yet been completed for the ARMv8 C2 port, and the absence of a good equivalent to AVX 512-bit is not helping (that gap will be closed when Arm delivers SVE). On top of that, the FFT scimark subbenchmark uses java.lang.Math functions (intrinsified for both x86 and ARMv8, the latter since JDK 11), which use the ARMv8 128-bit NEON SIMD. With scimark.large, this effect is mitigated because scimark.large does computations on a large dataset, which implies memory access, hence giving the ARMv8-based system the possibility to show a wider memory bandwidth. There is definitely some work ahead in order to bring the ARMv8 port scientific workload performance up to par with x86. However, right now it already can be concluded that for regular server-side Java business application workloads (data processing, XML, crypto operations), the OpenJDK 11 ARMv8 port running on ThunderX2 SKUs provides better performance compared to the x86 equivalent. Performance Diagnostics Performance diagnostics tools are essential for understanding the bottlenecks of a Java application being developed or run in production. Regular performance diagnostics through JMX and JVMTI API work on Arm just like they do on x86. For more thorough Java performance analysis, BellSoft also ported the AsyncProfiler and HonestProfiler to ARMv8 and contributed the changes back to the projects. This allowed for enhancing the performance of an application as complex as Hadoop on ARMv8. You can see a subset of the flamegraph from Hadoop Terasort benchmark collected from the Hadoop JVMs using AsyncProfiler on ARMv8 in Figure 3. If the reader is working on a complex Java application and would like to profile the JVM bottlenecks on Arm (or any other architecture), these are the open-source tools I would recommend. Figure 3. Flamegraph built with AsyncProfiler data running Hadoop on ARMv8. JFR, which was open-sourced by Oracle and contributed to OpenJDK 11 was also made available in Arm ports. Figure 4 shows method profiling output from JFR recording on JDK 11 ARMv8. As usual for JFR, the profiling overhead on ARMv8 was low (1-2%) and allowed to obtain detailed profiling information, which is very suitable for production system monitoring. Figure 4. OpenJDK Java Mission Control view of a JFR profile collected from Map Hadoop task on ARMv8. Java Ecosystem on Arm In theory, all software written in Java should just be able to work on Java on Arm. That said, some big projects make specific tweaks that tie them to a specific architecture, such as using natively-built libraries (for example, snappy). The following popular projects, though not claiming official support for ARMv8, were tested to work well on Arm: Hadoop 3.1.0, Tomcat 9.0.8, Spark 2.3.0, Kafka 1.1.0, Cassandra 3.11.2, Lucene 7.3.0, Flink 1.4.2. Future developments A number of companies including Arm, BellSoft, Cavium/Marvell, Linaro, Oracle, Red Hat, and others collaborate in the OpenJDK codebase to ensure the long-term future of upstream Arm ports. That includes gradual improvement in performance and stability, as well as working on a fully-supported Graal VM and Graal as a JIT compiler on ARMv8, ZGC, and such future projects as Valhalla and Panama. Aside from that, work is underway to ensure good use of the planned SVE instructions. Conclusion The upstream ARM 32-bit and ARMv8 Java ports are ready for production use, with all of the relevant features on par with x86. The 32-bit Arm port provides all the necessary functionality for Embedded & IoT deployments, including C1 for fast startup, low dynamic memory footprint and Minimal VM which allows for producing Java Runtime images with a low static footprint (under 16 Mb). It works well on such popular devices as the Raspberry Pi and, after proper device and application specific tuning, it is possible to use the 32-bit Arm port in production under the GPL license. The ARMv8 port which is aimed primarily at the server market, shows better performance results when compared to x86 on equivalent hardware (16% advantage in SPECjbb2015 Critical-jOPS, 33% advantage in SPECjbb2015 Max-jOPS and 28% advantage in SPECjvm2008 base composite compared to x86). As demonstrated by the SPECjvm2008 benchmarks for typical server-side Java business applications which would process and encrypt data and XMLs, the OpenJDK 11 ARMv8 port running on ThunderX2 is faster than the Intel SKU counterpart. The reasons why the SPECjvm2008 scimark.small performance is lower compared to Intel were analysed and should not be too difficult to improve. Overall, the Java software ecosystem is ready for production deployments on Arm. For Embedded & IoT use cases, Arm is already the primary platform of choice, but why would the server market and major cloud providers consider moving to a different architecture if the performance advantage is only tens of percents? The answer to that is price for performance. Considering the performance of the JVM on Arm, and the price of the CPUs, this starts to make sense. And it becomes very easy to try, considering how small the efforts are to take existing Java applications to a new architecture, provided there is a fully functional, performing and stable Java.
- [BellSoft at Linaro Connect 19 BKK](https://bell-sw.com/events/2019/02/13/Events-Linaro-Connect-BKK/): Dmitry Chuyko, BKK19-203 JVM on ARM. From sensor to cloud A variety of Java virtual machines on ARM have been around for a long time. Nowadays, OpenJDK makes it easy and secure to receive and process data at all stages. Using modern expressive language Kotlin and Docker containers, we will program a simple, but secure gateway that controls a sensor. We’ll demonstrate further data processing in the cloud as BigData and then visualization for end user using OpenJFX. We will discuss additional features such as deployment and provisioning and also how the new release model of the Java platform is connected to security. Event web site
- [JetBrains and BellSoft enter strategic collaboration](https://bell-sw.com/announcements/2019/02/13/JetBrains-BellSoft-Liberica-OpenJDK/): BellSoft will provide security patches and critical updates to JetBrains Runtime in addition to its own Liberica JDK JetBrains Runtime is a runtime environment for running IntelliJ Platform-based products on Windows, macOS, and Linux. JetBrains Runtime is based on the OpenJDK project with some value-add for JetBrains customers. These benefits include Subpixel Anti-Aliasing, enhanced font rendering on Linux, HiDPI support, ligatures, San Francisco font family support on macOS, and so on. BellSoft is one of the Top-5 OpenJDK contributors. They release and support Liberica JDK - an OpenJDK binary distribution verified by TCK for Java SE standard compliance. Liberica JDK 8 and Liberica JDK 11 are long-term supported versions (LTS) which will be receiving all critical patches and security updates at least until 2026. JetBrains collaborates with BellSoft to keep the security and stability level of JetBrains Runtime as high as its done for Liberica JDK. “Working with BellSoft allows us to keep our developers focused on areas, which are critical for our products’ functionality and allow BellSoft engineers to take care of security fixes and critical updates for JetBrains Runtime”,- JetBrains Runtime lead, Konstantin Bulenkov.
- [Premium Support is available for Liberica JDK](https://bell-sw.com/announcements/2019/03/26/premium-support-java/): Announcing Premium Support service, BellSoft now offers 24x7 Worldwide Coverage for Liberica JDK Support with 48 hours vulnerability patches SLA. Expanding its Standard Support BellSoft introduces Premium Support service, taking legendary excellence in customer service to new heights. San Jose, The United States of America: BellSoft (USA) has introduced a Premium support service. Since 2017, BellSoft has provided Standard technical support during business hours for Liberica JDK — including free security patches and critical updates. Now, BelLSoft’s new Premium Support addresses the high-end service requirements of Cloud providers, Banks, Stock Trading companies, Telecom carriers, and other organizations that need guaranteed response time and timely issue resolution or evening and weekend service. “We’re responding to customers,” said Aleksei Voitylov, BellSoft CTO. “They asked for service-level agreements with guaranteed response time and quick release for vulnerability and critical patches. Our Premium support service enhances Standard Support with global 24x7 coverage and 48 hours SLA for security updates.” Premium Support offers world-wide, around-the-clock service anytime, day or night. BellSoft support staff, which made the company one of Top-5 OpenJDK contributors are available 24 hours a day, 7 days a week to address major or critical incidents anywhere in the world About BellSoft BellSoft was founded in 2017 by former Oracle Java team. BellSoft participates into Java technology evolution, and the company is among Top-5 most active OpenJDK upstream contributors together with Oracle, Red Hat, SAP, and Google. BellSoft provides and supports Liberica JDK - Java binary distribution based on OpenJDK which can be used as an alternative of Oracle JDK 8, 11 and beyond. Liberica JDK is available for a variety of platforms and targets server, cloud, desktop, and embedded use cases. Liberica JDK guarantees compliance with Java SE standard as verified by TCK test suite. BellSoft provides security updates and critical patches for Liberica JDK in parallel with Oracle Java SE CPU updates. The company offers commercial support for Liberica JDK users and is committed to supporting Liberica JDK 8 and 11 until at least 2026. Besides, BellSoft provides services of complex Open Source project optimization, for example, gcc and LLVM compilers, OpenJDK, MySQL database, Hadoop stack, and others.
- [Linux package repositories for Liberica JDK](https://bell-sw.com/announcements/2019/03/28/linux-repositories/): Announcing public availability of repositories for .deb and .rpm-based Linux distributions Keeping JDK secure requires ease of update and tight integration with operating systems default installation techniques. Aside from the already available Docker repositories, we are happy to announce public availability of YUM and APT Linux packages reporitories for all supported versions of Liberica JDK, which includes security patches and critical updates. This allows customers and users to ensure security and integrity of Liberica JDK installations. RPM repositories are known to work with supported versions of Centos, RHEL, Oracle Linux, Fedora and openSUSE. APT repositories are known to work with Ubuntu, Debian and Raspbian. Please refer to installation guide for specific Liberica JDK version for detailed installation instructions on using a Linux repository. APT Repository (.deb-based Linux distributions) Add BellSoft official GPG key and setup the repository sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com --recv-keys 32E9750179FCEA62 echo "deb [arch=amd64] https://apt.bell-sw.com/ stable main" | sudo tee /etc/apt/sources.list.d/bellsoft.list Update repositories and install Liberica JDK packages sudo apt-get update sudo apt-get install bellsoft-java11 YUM Repository (.rpm-based Linux Distributions) Add BellSoft official GPG key and set up the repository gpg --keyserver keys2.kfwebs.net --recv-keys 32e9750179fcea62 gpg --export -a 32e9750179fcea62 | sudo tee /etc/pki/rpm-gpg/RPM-GPG-KEY-bellsoft > /dev/null echo | sudo tee /etc/yum.repos.d/bellsoft.repo > /dev/null << EOF [BellSoft] name=BellSoft Repository baseurl=https://yum.bell-sw.com enabled=1 gpgcheck=1 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-bellsoft priority=1 EOF Update repositories and install packages sudo yum update sudo yum install bellsoft-java11 Available packages For Liberica JDK 8, the following packages are available: bellsoft-java8 contains the full Liberica JDK, including LibericaFX for platforms which support it. bellsoft-java8-runtime includes Liberica JRE, including LibericaFX for platforms which support it. For Liberica JDK 11 and on, the following packages are available: bellsoft-java11 contains the full Liberica JDK, including LibericaFX and a variety of JVMs for platforms which support it. bellsoft-java11-lite includes Liberica JDK with compressed modules and Server VM, without any extra packages. Supported CPU Architectures Packages for all architectures supported by Liberica JDK are available in the repository. At the time of writing, the list is as follows: Liberica JDK 8: amd64, i386, armhf Liberica JDK 11: amd64, arm64, armhf Liberica JDK 12: amd64, i386, arm64, armhf Switching default version of Liberica JDK Liberica JDK packages, when installed change the default association for related executables. Different major versions of Liberica JDK can be installed in parallel in the same environment. The most major version installed becomes the default one. On .rpm-based Linux operating systems, use update-alternatives command to switch between the default installed versions of runtime. On .rpm-based Linux operating systems, use update-java-alternatives command to switch between the default installed versions of runtime.
- [BellSoft at JBCNConf in Barcelona](https://bell-sw.com/events/2019/04/11/Events-JBCNConf/): Dmitry Chuyko, Do not put all eggs in one container Microservice architecture and containerization have become the standards of modern application development. The challenges that developers face today are different from the problems that we used to solve earlier. Creators of the Java runtime respond to this with the appropriate functionality in the JDK. For example, an inexpensive cloud instance can be quite powerful. And it runs a bunch of containers. Then JVMs running in different containers compete for instance resources. Starting from Java 10, virtual machines know how to live in peace, this work has continued in Java 11. On the other hand, you now need to choose which base image to use. This includes the choice of operating system and Java runtime. OS images vary greatly in size and have their own characteristics, which must be taken into account. Java runtime is also for every taste now. And even within the OpenJDK framework, assemblies from different companies are available with different functionality and size. And you can create a custom runtime image. We will consider the practical application of Java 11 functionality in a container environment typical for popular fram. Event web site
- [OpenJDK may have a new home soon](https://bell-sw.com/announcements/2019/04/23/OpenJDK-project-git/): The OpenJDK projects available on GitHub For those who are not just looking for Java download, there is interesting news. You may be heard about Project Skara, which intent is moving OpenJDK repository from Mercurial to Git. Using Git will bring an obvious benefit: modern code management infrastructure with pull requests. However, there might be another positive side effect, the OpenJDK community may attract millennials into Java evolution. Last week Robin Westberg announced at skara-dev mailing list that new OpenJDK projects now have read-only repositories on GitHub. Using GitHub simplifies working with repositories for Java developers who want to better understand the runtime internals. It is easier and faster to clone the repositories to build experimental projects. The following read-only repositories are available today: Current/JDK 13: openjdk/jdk JDK 12 Updates: openjdk/jdk12u JDK 11 updates dev: openjdk/jdk11u-dev JDK 11 updates: openjdk/jdk11u Client Libraries: openjdk/client Project Shenandoah: openjdk/shenandoah Project Tsan: openjdk/tsan Project Amber: openjdk/amber Project Valhalla: openjdk/valhalla Project Panama: openjdk/panama Project Loom: openjdk/loom Project Metropolis: openjdk/metropolis Project Portola: openjdk/portola
- [Liberica JDK enhances developer experience with package managers](https://bell-sw.com/announcements/2019/05/15/dev-repositories/): SDKMAN! and Homebrew taps now available Developers all over the world like the ability to have multiple JDK versions installed on their development machines. Choosing the JDK version to work with, being it cutting-edge Liberica JDK release or production-ready supported LTS release has to be easy. Aside from the already available Docker, YUM and APT repositories, we are happy to announce public availability of Liberica JDK in SDKMAN! and Homebrew. Liberica JDK packages for SDKMAN! and Homebrew include LibericaFX, which is based on OpenJFX, and are available for the following architectures: SDKMAN!: Windows x86_64, Mac, Linux x86_64, Linux x86 Homebrew: Mac, Linux x86_64 SDKMAN! cheatsheet Install SDKMAN! as instructed here. List the available versions of Liberica JDK: sdk list java Install desired Liberica JDK version: sdk install java 11.0.3-librca Switch between versions of Liberica JDK (assuming you have several installed): sdk use java 12.0.1-librca Set a sepecific version of Liberica JDK as default: sdk default java 8.0.212-librca Check which version of Liberica JDK you have currently selected: sdk current java Homebrew cheatsheet Install Homebrew as instructed here. Add Liberica JDK tap: brew tap bell-sw/liberica Install Liberica JDK. On Mac: brew cask install liberica-jdk11 On Linux: brew install liberica-jdk11 The list of available Liberica JDK versions in Homebrew can be found at bell-sw/homebrew-liberica
- [BellSoft at Oracle CodeOne 2019](https://bell-sw.com/events/2019/06/05/Events-CodeOne2019/): Dmitry Chuyko and Alex Belokrylov, Do not put all eggs in one container We will be happy to meet you at Oracle CodeOne conference in San Francisco, September 15 - 19. It is our honor to share the knowledge and experience we obtained in the OpenJDK project. If you want to meet us, please drop an email at info@bell-sw.com or reach me on twitter @gigabel. Microservice architecture and containerization have become the standards of modern application development. The challenges that developers face today are different from the problems that we used to solve earlier. Creators of the Java runtime respond to this with the appropriate functionality in the JDK. For example, an inexpensive cloud instance can be quite powerful. And it runs a bunch of containers. Then JVMs running in different containers compete for instance resources. Starting from Java 10, virtual machines know how to live in peace, this work has continued in Java 11. On the other hand, you now need to choose which base image to use. This includes the choice of operating system and Java runtime. OS images vary greatly in size and have their own characteristics, which must be taken into account. Java runtime is also for every taste now. And even within the OpenJDK framework, assemblies from different companies are available with different functionality and size. And you can create a custom runtime image. We will consider the practical application of Java 11 functionality in a container environment typical for popular frameworks. Event web site
- [Liberica JDK 13 EA with OpenJFX 13 on the Raspberry Pi 4](https://bell-sw.com/announcements/2019/09/12/JDK-JavaFX-Video-Preview/): Using Liberica JDK 13 EA to play Media with OpenJFX 13 on the Raspberry Pi 4 Today we published Liberica JDK 13 Early Access builds on all supported platforms. As it currently stands, BellSoft provides the most number of platforms publicly for latest Liberica JDK compared to other OpenJDK distribution vendors. We encourage users to try the preview of all the new features of OpenJDK 13: Dynamic CDS Archives ZGC: Uncommit Unused Memory Reimplement the Legacy Socket API Switch Expressions (Preview) Text Blocks (Preview) With Liberica JDK 13 we will be celebrating the 6th major release of Liberica JDK, so we want to thank our supporters and users with a special gift: Liberica JDK 13 EA now supports Raspberry Pi 4, the most popular single board computer among enthusiasts. This version adds much more processing capabilities compared to Raspberry Pi 3, that’s why we decided to extend the Java FX Modules supported on the Raspberry Pi by adding OpenJFX Media support on top of OpenJFX Graphics and Controls. Liberica JDK on the Raspberry Pi also now supports Raspbian Buster OS release. Since Liberica JDK 13 EA builds for ARM 32 Hard Float now include OpenJFX Media, we encourage users to try it out on the Raspberry Pi 4 using the following steps: Install Raspbian Buster image on the Raspberry Pi 4. Disable “dtoverlay=vc4-fkms-v3d” boot setting in /boot/config.txt by commenting the relevant line and reboot the Pi. Run “sudo apt-get install libavcodec-extra libavformat58 libavcodec58 gstreamer1.0-libav” to install the gstreamer backend and relevant codecs. Install Liberica JDK 13 EA from here. Download an H.264 Video sample from here. Compile the following VideoTest example with javac. Or, better yet, develop your own! Run the VideoTest example, passing the Video sample as command line parameter: java VideoTest simpsons_movie_trailer.mp4 VideoTest.java: import javafx.application.Application; import javafx.scene.Scene; import javafx.stage.Stage; import java.io.File; import javafx.geometry.Pos; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import javafx.scene.media.Media; import javafx.scene.media.MediaPlayer; import javafx.scene.media.MediaView; import javafx.scene.text.Text; public class VideoTest extends Application { static String arg0; public static void main(String[] args) { arg0 = args[0]; Application.launch(args); } @Override public void start(Stage primaryStage) { StackPane root = new StackPane(); MediaPlayer player = new MediaPlayer( new Media(getClass().getResource(arg0).toExternalForm())); MediaView mediaView = new MediaView(player); root.getChildren().add( mediaView); Scene scene = new Scene(root, 1024, 768); primaryStage.setScene(scene); primaryStage.show(); player.play(); } } On the Raspberry Pi 4 we noticed OpenJFX could play 720p and 1080p video with the same quality as using native mplayer/gstreamer. Make sure you use a cooler with your Raspberry Pi 4 - unlike the Raspberry Pi 3, that thing gets hot! In this EA release only the X11 GTK backend and H.264 for video playback are known to work. Adding ES2 and Direct FrameBuffer is in the works, and will be made available as soon as some glitches in the Raspberry Pi 4 video driver are resolved. Video playback takes some time to set up, but after all it feels like a feature you always expected to have. Your feedback is very welcome!
- [4 Things you Don’t Want To Do at Code One 2019](https://bell-sw.com/announcements/2019/09/13/CodeOne-2019/): Code One 2019 Next week we’ll be at CodeOne in San Francisco. Hopefully, you will be there too, to learn what’s new and Discover the Latest on Java. To help you on your quest for perfect event experience, we created a shortlist of what you may not want to do at Code One. 1. Don’t miss the Raspberry Pi supercomputer As @chrisbensen was tweeting about Building the Raspberry Pi SuperComputer and blogging about the Very Large Raspberry Pi Cluster, we got more and more excited! Many have asked why would someone ever connect over a 1000 Raspberry Pi’s? Well, it’s the wrong question. Better to ask: “Why wouldn’t you connect them if you can?” :) We are huge fans of Raspberry Pi and recently released the early access of Liberica JDK for Raspberry Pi 4. We want to see the supercomputer and make many selfies with it and trust us, and you don’t want to miss this Code One attraction too! 2. Don’t sleep Sleep is healthy, sure. However, sometimes, there is not enough time for that. The exhibition floor is exciting, with many vendors to speak with and later visit the offsite events. The sessions are beneficial. Moreover, the iconic Golden Gate views are there to take your breath away. So sleep can wait a bit. 3. Don’t be afraid to speak with people Networking is so popular that some people stay away from it. We have a different feeling. At such a huge developer event as Code One, you want to learn more and get to know more people that love the same things as you do. Like, Java. So Code One is just great to be more engaged with the community and get involved. You can volunteer for Code One 4 Kids to help the Next Generation of Developers with their Workshop Projects. Getting involved is a great way to meet people who have similar ideas and similar concerns as you do. Sharing stories with them is one of the most valuable things about Code One. 4. Our last “Don’t” is a bit self-serving Don’t miss the BellSoft Booth 3112 We worked hard to give you Liberica JDK and the Guardians of the Enterprise experience at Code One. Fun superhero tees along with talks with the developers of Liberica JDK and the top-5 contributors to OpenJDK and a super-cool demo with the Nvidia Jetson Nano platform. NVIDIA Jetson Nano is a low-power platform which can be used to create AI systems. For instance Network Video Recorders (NVRs), home robots, and intelligent gateways with full analytics capabilities. The Nvidia Jetson is ready and set at BellSoft boot 3112 for pass-through people traffic count. This installment helps participants of Code One receive verified data about their booth popularity within the conference. Technically we have a Jetson Nano board with 4K camera which captures video and uses DeepStream library for real-time video analytics & people identification. Java application is used to perform data analysis, and the OpenJFX application is visualizing the stats. Come to our Booth 3112 at Code One to learn more about Liberica JDK and the Guardians of the Enterprise, see Nvidia Jetson Nano in use, and make new friends!
- [Liberica JDK is generally available on PowerPC](https://bell-sw.com/announcements/2019/09/25/Java-on-PowerPC/): Based on OpenJDK and verified by TCK for Java SE Standard Compliance, Liberica JDK for PowerPC is designed to deliver a more efficient end-to-end developer experience by making the runtime systems more hardware-agnostic. BELLSOFT ANNOUNCES THE GENERAL AVAILABILITY OF LIBERICA JDK ON POWERPC TO INCREASE THE RANGE OF SUPPORTED HARDWARE PLATFORMS San Jose, CA, September 12, 2019 — BellSoft, the world’s top 5 contributor to Java OpenJDK and provider of open-source solutions, announced today the general availability of Liberica JDK for all supported versions of PowerPC. Releasing Liberica JDK for PowerPC makes BellSoft the world’s leader by the number of publicly supported platforms for JDK 11. By announcing the general availability of Liberica JDK for PowerPC, BellSoft increases the range of supported hardware platforms of its OpenJDK binary distribution. Liberica JDK customers are now able to take advantage of one-vendor supported Java runtime across all popular architectures. In addition to the already supported x86, ARM, and SPARC, Liberica JDK runs on PowerPC 8 and 9 servers and embedded solutions powered by PowerPC architecture. Liberica JDK for PowerPC is an underlying runtime responsible for secure and cross-platform application execution. By integrating Liberica JDK 11 natively into the PowerPC Linux platform, developers can improve their application modularity and leverage the new Java language features. Platform operation teams can benefit a TCK-verified JDK 11 runtime with LTS support. Users can benefit from freely available JDK 12 Runtime on PowerPC built on open-source software, which includes all the latest features. As with the features and components of Liberica JDK for PowerPC, Liberica JDK is backed by BellSoft’s support, which makes it a powerful option for production systems in even the most mission-critical roles. Additionally, BellSoft services are available, providing technical expertise and strategic advice to customers of Liberica JDK for PowerPC. Availability Liberica JDK for PowerPC is available for download on the BellSoft website: https://bell-sw.com/pages/downloads/?version=java-11 Learn more with BellSoft at Oracle CodeOne 2019, booth 3112, September 15-19 in San Francisco. Supporting quotes Alex Belokrylov, CEO of BellSoft “Liberica JDK gives the full power of running Java applications across a variety of popular CPU architectures both on-prem and cloud-based. Organizations running a multi-vendor infrastructure will benefit from using publicly available, supported and Java SE Standard Compliant binaries for x86, ARM, SPARC, and PowerPC architectures.” About BellSoft BellSoft releases and supports Liberica JDK, an OpenJDK binary distribution verified by TCK for Java SE Standard Compliance. BellSoft engineers have been contributing to the OpenJDK project since its inception. Being one of the top 5 OpenJDK contributors, BellSoft drives a community-powered approach to deliver reliable and compact containers with Liberica JDK targets microservice solutions on. Using popular as well as in-house performance optimization techniques and tools allows BellSoft to work on performance tuning for industry leaders.
- [Oracle Code One 2019: Side Notes](https://bell-sw.com/announcements/2019/09/29/BellSoft-at-CodeOne2019-side-notes/): On September 16-19, the Oracle Code One conference 2019 threw open its doors to Java developers hailing from all over the world. It encompassed all possible subjects a developer might be interested in: from cloud computing, data science, and the professional community to new tools, devices, and technologies. Just like in 2018, the conference was attended by more than 7,000 people, which makes it the world’s most popular event for software developers. For BellSoft, it all started with Geek Bike Ride through San Francisco, a traditional cycling trip for Java enthusiasts which dates back to the time when the conference was called JavaOne. This ride established positive energy continuing throughout each day of the conference. As the OpenJDK project switched to a twice-yearly release calendar, delivering a new version every March and September, one of the most anticipated highlights of the conference was the launch of the new Java 13. Georges Saab, Oracle’s VP, made the announcement on September 17th and presented the new version’s key features: Z Garbage Collector enhancements, application class-data sharing, and previews of switch expressions and text blocks. Of course, this wasn’t a surprise for us, and we released our Liberica JDK 13 in no time at all. Shortly before the conference, we also dropped the news of the general availability of Liberica JDK for PowerPC, which made BellSoft the world’s leader by the number of publicly supported platforms for JDK 11. But really for us, this high profile summit is not just another chance to stay on top of the news about the ever-evolving Java ecosystem, but also a venue to rub elbows with our friends from the Java development team and the hyper-engaged community of Liberica JDK users. Speaking of the latter, so many unique projects are unfolding within the Java ecosystem as a whole, and especially Liberica JDK! For instance, it was nicely encouraging to see the team of Robo4J; they’re developing a framework for robots and IoT devices based on Liberica JDK. If you run your product on Liberica JDK too, please tag us anywhere online by including #LibericaJDK #BellSoft in your post on Twitter. We are eager to learn about more projects using Liberica JDK! We’re already looking to the horizon for the next Code One event, as 2020 only promises to bring a good many terrific new features to the platform. San Francisco, we can’t wait to see you again!
- [Meet BellSoft performance engineer at Devoxx Belgium](https://bell-sw.com/events/2019/10/03/Events-Devoxx-Belgium-2019/): Java on ARM. Theory, Applications and Workloads, Dmitry Chuyko, Wednesday from 15:10 to 16:00 in Room 6 There are many great events for developers around the world. However, for the European Java community, there is an outstanding one. It is Devoxx Belgium. If you are there, pay attention to Dmirty Chuyko talk about today and tomorrow of OpenJDK on ARM. We are not talking about Raspberry Pi; Arm is much more than that. Can you imagine a server with hundreds of cores perform on the same level as other CPUs but with lower power consumption? It is a reality which Dmitry will explain in detail. In his talk “Java on ARM. Theory, Applications and Workloads” scheduled on Wednesday from 15:10 to 16:00 in Room 6 https://devoxx.be/talk/?id=13052 he explores the status of Java and the Java ecosystem on ARM, together with the Java ARM port features and performance of specific workloads. We will be happy to meet you at Devoxx!
- [Liberica JDK optimized packages introduction](https://bell-sw.com/announcements/2020/01/15/Liberica-JDK-8u242-11.0.6-13.0.2/): Today, with the announcement of the general availability of Liberica JDK 8u242, 11.0.6, 13.0.2, BellSoft introduces several changes that will help Liberica JDK customers have a better experience with the product. Since the first release that happened in 2017, we continue to work on the improvements that make Liberica JDK the most useful and practical OpenJDK distribution in the world. Today, we extend the binary package offering and will be delivering Liberica JDK in three forms: Full, Standard, and Lite. The Full version of Liberica JDK includes all production-ready features of OpenJDK and some add-on packages, for example, OpenJFX, MinimalVM, Device Input-Output API on selected platforms. The Standard version targets most of the use-cases of OpenJDK, including both server and desktop. Lite is the tiniest version of Java SE standard verified OpenJDK binary created to allow high-density deployments in cloud infrastructure. As you may know from our conference talks, we use a hybrid environment to build and test Liberica JDK. And we know from practice that cloud infrastructure is a costly resource that has to be used wisely. Liberica JDK allows you to save your cloud resources by keeping only the required components within the distribution. Now you have even more freedom of choice to decide on Liberica JDK’s form that ideally fits the unique environment you have.
- [Meet BellSoft engineers at JFokus in Stockholm](https://bell-sw.com/events/2020/02/02/Events-JFokus-Stockholm/): Check how small can be a Docker container with fully functional JDK at BellSoft Booth. It is the second time BellSoft is going to JFokus to meet a vibrant Sweden Java community. Find us on February 4-5, 2020 in Stockholm at Waterfront Conference Center. At BellSoft booth, you can meet engineers who made the smallest Docker container with Java SE standard verified Liberica JDK, which allows a higher density cloud deployments and saves valuable resources. If you want to know, many useful OpenJDK features for a containerized application join the talk of Dmitry Chuyko on February 4 at 15:35, Room A2, Don’t put all your eggs in one container. See you at JFokus!
- [Liberica Mission Control 7.1 released](https://bell-sw.com/announcements/2020/02/04/Java-Mission-Control-7-1/): Liberica Mission Control (LMC) 7.1 is generally available for free download from the BellSoft website. LMC provides developers and production monitoring teams with comprehensive insights into Java runtime and helps identify performance issues on the go with minimal overhead. Liberica Mission Control represents the data collected or recorded live during Java application runtime. It is compatible with Liberica JDK 11+ and other OpenJDK builds, including Oracle Java SE with the Java Flight Recorder (JFR). Liberica Mission Control helps you diagnose how your applications execute in production, so you can observe and troubleshoot Java performance pitfalls that happen under real-world circumstances, for instance, OutOfMemoryError exceptions and lock contention. Even more, it could sometimes highlight lines of code that you were not even aware were adding worthless overhead to your programs. Liberica Mission Control 7.1 brought more than 60 enhancements, performance, and critical updates and is available for Windows, Linux, and macOS. Liberica Mission Control is supported within the Liberica JDK support subscription.
- [Liberica JDK 14
is generally available](https://bell-sw.com/announcements/2020/03/18/Liberica-JDK-14/): By changing the cadence of OpenJDK releases two years ago, Oracle accelerated the evolution of Java, which was categorically a positive move for the platform and the ecosystem. Before the change, developers were waiting for the new version with stable features for at least three years. Today, we can admit that the Java run-time development process is iterative. Initially, new capabilities are injected into OpenJDK as preview features; the developer community is testing and giving its feedback to JEP leaders, and after a number of releases, the feature becomes standard. JDK 14 has sixteen new features in six areas: API, Runtime & Performance, Serviceability, Tools, Improved platforms support, and Deprecation. API enhancements JEP-361: Switch Expressions (Standard) JEP-305: Pattern Matching for instanceof (Preview) JEP-359: Records (Preview) JEP-368: Text Blocks (Second Preview) JEP-370: Foreign-Memory Access API (Incubator) Run-time and Performance JEP-358: Helpful NullPointerExceptions JEP-352: Non-Volatile Mapped Byte Buffers JEP-345: NUMA-Aware Memory Allocation for G1 Serviceability JEP-349: JFR Event Streaming Tools JEP-343: Packaging Tool (Incubator) Improved platforms support JEP-364: ZGC on macOS JEP-365: ZGC on Windows Deprecation JEP-362: Deprecate the Solaris and SPARC Ports JEP-366: Deprecate the ParallelScavenge + SerialOld GC Combination JEP-363: Remove the Concurrent Mark Sweep (CMS) Garbage Collector JEP-367: Remove the Pack200 Tools and API Let’s look at some new features in details. API enhancements Switch Expressions (Standard) JEP-361 This feature extended switch statement, which can be used as an expression with the help of arrow (->), and now can yield/return the value. Before JDK 14 switch (day) { case MONDAY, FRIDAY, SUNDAY -> System.out.println(6); case TUESDAY -> System.out.println(7); case THURSDAY, SATURDAY -> System.out.println(8); case WEDNESDAY -> System.out.println(9); } switch (day) { case MONDAY: case FRIDAY: case SUNDAY: System.out.println(6); break; case TUESDAY: System.out.println(7); break; case THURSDAY: case SATURDAY: System.out.println(8); break; case WEDNESDAY: System.out.println(9); break; } JDK 14 JDK 14 makes the syntax more precise and removes the boilerplate. switch (day) { case MONDAY, FRIDAY, SUNDAY -> System.out.println(6); case TUESDAY -> System.out.println(7); case THURSDAY, SATURDAY -> System.out.println(8); case WEDNESDAY -> System.out.println(9); } Pattern Matching for instanceof (Preview) JEP-305 Before JDK 14 if (o instanceof Foo) { Foo f = () o; System.out.println(f.getName()); } JDK 14 if (o instanceof Foo f) { System.out.println(f.getName()); } This is the first step to other changes. But it’s already possible to combine Switch expressions with instanceof pattern matching and create much cleaner code. Before JDK 14 @Override public void onReceive(Object msg) { if (msg instanceof ClusterMessage) { onMsg((ClusterMessage) msg); } else if (msg instanceof RpcBroadcastMsg) { onMsg((RpcBroadcastMsg) msg); } ....... } JDK 14 @Override public void onReceive(Object msg) { switch (msg) { case ClusterMessage cm -> onMsg(cm); case RpcBroadcastMsg rbm -> onMsg(rbm); ... } } Records (Preview) JEP-359 Before JDK 14, a data class would look like below with getters, setters, equals, hashcode, toString, and so on. To somehow get rid of all that mess, a widely adopted Lombok library was created. Also, you could rely on your IDE. public class Station { private String name; private Coordinates coordinates; public String getName() { return name; } public void setName(String name) { this.name = name; } public Coordinates getCoordinates() { return coordinates; } public void setCoordinates(Coordinates coordinates) { this.coordinates = coordinates; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; PlainStation that = (PlainStation) o; return Objects.equals(name, that.name) && Objects.equals(coordinates, that.coordinates); } @Override public int hashCode() { return Objects.hash(name, coordinates); } @Override public String toString() { return "PlainStation{" + "name='" + name + '\'' + ", coordinates=" + coordinates +s '}'; } } With JDK 14, the same data-class can be declared as Record with only one line of code. The class will already have all getters, setters etc. public record RecordStation(String name, List<Coordinates> coordinates) {} Text Blocks (Second Preview) JEP-368 In JDK 13, Text Blocks was introduced with JEP-355. JDK 14 evolves (or, better say, reconsiders) this feature, which is useful for some DSLs. For example, we used it for JOCL (Java binding for OpenCL) private static String vertexShaderSource = """ #version 150 core in vec4 inVertex; in vec3 inColor; uniform mat4 modelviewMatrix; uniform mat4 projectionMatrix; void main(void) { gl_Position = projectionMatrix * modelviewMatrix * inVertex; } """; The new Java language features have well-known analogs in Kotlin. Switch Expressions –> When Expression Records –> Data Classes Text Blocks –> Multiline String Literals These features are already supported since IntelliJ IDEA 2020.1, while JetBrains engineers, together with Oracle and other OpenJDK contributors, worked on feature design and implementation. Foreign-Memory Access API (Incubator) JEP-370 There are some third-party tools like Ignite, mapDB, and memcached to work with off-heap memory. Now Java will have its native API to do the same with new abstractions MemorySegment, MemoryAddress, and MemoryLayout. Deprecation Evolution is not just about adding new capabilities. Evolution is also a removal of rudiments. A new cadence allows quick disposal of outdated features. In JDK 14, the Solaris port will be decommissioned. Solaris 11 will be the end of support in 2035. Users who need to have Java for Solaris should stay on LTS versions: JDK 8 or JDK 11. To download Java SE verified binaries of Liberica JDK 14, go here
- [BellSoft and VMware to Work Together on OpenJDK Evolution](https://bell-sw.com/announcements/2020/06/03/BellSoft-and-VMware-to-Work-Together/): BellSoft, a leading OpenJDK contributor, announced work with VMware, a leading innovator in enterprise software. BellSoft will provide their main product, Liberica JDK and full-service support for VMware Tanzu. BellSoft, being a leading OpenJDK contributor, and VMware will join forces and work closely with the OpenJDK community to bring Java runtime to the next level of usability and performance, maintaining its exceptional reliability. Liberica is an OpenJDK binary distribution verified by TCK for Java SE Standard Compliance. “We want to bring industry-first innovations with our partners to provide customers with fast and professional support, high level of trust and flawless security capabilities. We are really happy to become a partner of such a comprehensive ecosystem and we believe these plans have earned us recognition from today’s leading specialist,” says Alex Belokrylov, CEO of Bellsoft. The beneficial partnership Together these two leading companies and their teams will work on improving the Java platform and developing the user community. VMware customers will benefit from using Liberica JDK, supported in a transparent and Open Source way to run, build, and manage their most critical applications with additional assurance in runtime quality and security. Liberica JDK support team will service VMware’s customer requests and resolve any—even the most complicated—Java runtime problems, including unforeseen security and complex performance issues. VMware will receive a reliable partner with whom they can promptly discuss the most profound issues and non-trivial cases. About Bellsoft BellSoft releases and supports Liberica JDK, an OpenJDK binary distribution verified by TCK for Java SE Standard Compliance. BellSoft engineers have been contributing to the OpenJDK project since its inception. Being one of the top-5 OpenJDK contributors, BellSoft drives a community-powered approach to deliver reliable and compact containers with Liberica JDK targets microservice solutions on. Using popular as well as in-house performance optimization techniques and tools allows BellSoft to work on performance tuning for industry leaders. Website: bell-sw.com About VMware VMware software powers the world’s complex digital infrastructure. The company’s cloud, networking and security, and digital workspace offerings provide a dynamic and efficient digital foundation to customers globally, aided by an extensive ecosystem of partners. Headquartered in Palo Alto, California, VMware is committed to being a force for good, from its breakthrough innovations to its global impact. For more information, please visit www.vmware.com/company.html.
- [How to Notarize a Mac Application with Liberica JDK using javapackager](https://bell-sw.com/announcements/2020/06/10/How-to-Notarize-a-Mac-Application-with-Liberica-JDK/): One of the main global trends in IT is the safety of users and preventing the spread of malicious programs, but at the same time, it greatly complicates the life of developers. There are different approaches to solving the issue associated with software security. The most common is the use of an antivirus tool by scanning a computer for malware. A complementary approach was taken by Apple, which introduced the Gatekeeper software to force code signing and check downloaded applications before allowing them to run. The trend is moving towards deanonymization, the violation of anonymity when customers of compromised software have their personal data leaked. The need for notarization arises as a counter to this negative trend by giving users the confidence that a software package was created by an identified developer and checked for malware. Regarding security, an example is Windows Defender, which applies a signing code for verifying the compliance of a program to security requirements. Websites use SSL (Security Sockets Layer) certificates for the purpose of ensuring privacy, authentication, and data integrity in online communications. Apple, on the other hand, introduced notarization infrastructure. Regardless of who is notarizing software, the fact remains that it is an essential, albeit burdensome element of software development imposed by Apple as a requirement now. But there are ways to simplify and even ameliorate the process of notarization for applications in Java and JavaFX that are provided by distributors like Liberica JDK, thus making it a routine procedure, rather than an unpleasant allocation of time and resources. The main advantage of resorting to Liberica JDK is ensuring that applications bundled with the runtime as a single package will be able to pass the notarization procedure smoother and with greater ease. What is notarization? Why is it necessary? Notarization gives users more confidence that the Developer ID-signed software being distributed has been checked by Apple for malicious components. Notarization is not an App Review service, as the Apple notary service is an automated system that scans software for malicious content, checks for code-signing issues, and returns the results to the user quickly. If there are no issues, the notary service generates a ticket for the user to staple to their software. The notary service also publishes that ticket online where Gatekeeper can find it. When the user first installs or runs any new software, the presence of a ticket, either online or attached to the executable file, tells Gatekeeper that Apple notarized the software. Gatekeeper then places descriptive information in the initial launch dialog to help the user make an informed choice about whether to launch the app. Now, since the procedure has become mandatory, beginning with macOS Catalina 10.15, it is impossible to launch a non-notarized application. Liberica JDK is a notarized product. What will that give its customers and users? Liberica JDK is an alternative to Java and acts as the basis for application development and launching Java SE applications. As an officially recognized and certified application, Liberica JDK has gone through all the steps of notarization and provides full notarization compliance of its software. Liberica JDK binaries have already been notarized for a year since last summer. OpenJDK notarization is not an obvious process and requires a deep signing of all binaries and modules. And Liberica successfully passed the notarization procedure. In order to create an application and have it notarized, users need to refer to already notarized software. This is why using Liberica JDK is one of the easiest and most straightforward means of developing applications that will be recognized and notarized by the Apple notary service. By referring to the notarized Liberica JDK package as an alternative to Java, application developers will be able to receive a shortcut in having their software verified in the future. It is quite easy with Liberica JDK having a special build that facilitates the delivery of app bundles. The Javapackager element An important element of the application notarization procedure and an integral part of the Liberica JDK package. The Java Packager tool already comes with the JDK and allows packaging Java applications from the command line, thus being an alternative to other third-party tools. It is important to note that the Java Packager does not automatically produce a Jar file. Many other packager formats are available, including native executable ones tailored for various platforms. Liberica JDK’s Javapackager is ultimately applied during the build process, to create applications that would get easily notarized. Alternatively, it may be used as a standalone tool. The javapackager tool gives us several options: Create a macOS application image (for testing purposes) Create a notarized DMG image (suitable for distribution to end users) Create a notarized PKG installer (suitable for distribution to end users) In this article, we will demonstrate how to perform all three operations. But, if you are uncertain or simply do not want to do them on your own, contact our support team. Click the button below, provide us with your details, and you will get advice from expert architects, free of charge. Request Licensing What if you need to notarize your Java or JavaFX application? A guide Notarizing an application with the Liberica JDK package and the Javapackager is a straightforward and easy process. Remember to follow the steps thoroughly and use an unsigned copy of Liberica JDK. The resulting application will be notarized and have the necessary credentials to comply with the requirements set by Apple. To get the aforementioned unsigned copy of Liberica JDK, click the button below, leave your details, and our support team will gladly help you. If you want to make an app using Liberica JDK and Java Packager, you should walk through several stages: create a jar file containing your application, generate a bundle and notarize it. For the notarization to be successful, it is necessary to get a special build of Liberica JDK 8u252 for macOS, which allows creating a signed application that will be notarized. In this example, it is called bellsoft-jdk8u252+9-macos-amd64-full-nosign.zip. We use a special unsigned Liberica JDK 8u252 because the javapackager cannot re-sign already signed JDK files. Extract the unsigned Liberica JDK 8u252 bundle: unzip ./bellsoft-jdk8u252+9-macos-amd64-full-nosign.zip Also, before creating notarized bundles, do not forget to unlock the developer key: security unlock-keychain -p "${YOUR_PASSWORD}" We will next create installer bundles and illustrate this step with the Recaf app. Building an application JAR file Recaf version 2.0.0 will serve as an example since it has a better architecture to work as a standalone macOS application. First, we should build a Recaf jar file, create an application bundle and notarize it. The notary service verifies the app’s content; it goes through a jar file and checks that all native libraries in an executable file are signed. So, our job here is to create a Recaf file, check if it has a native part and sign it. Clone Recaf to your workspace: git clone https://github.com/Col-E/Recaf.git Check out the Recaf 2 branch: git checkout 2.0.0-redesign.10 Compile and package it using the build script or with mvn clean package If your build is successful, the resulting jar file will be placed in the target directory and called recaf-2.0.0-J8-jar-with-dependencies.jar. Java Packager takes the application main class as one of its inputs. If the application’s main class name is unknown, you can easily find it out: Type in the following command to extract the manifest from the jar file: ./jdk8u252.jdk-full-nosign/Contents/Home/bin/jar \ xf ./recaf-2.0.0-J8-jar-with-dependencies.jar \ META-INF/MANIFEST.MF Inspect the extracted MANIFEST.MF to find the main class: grep Main-Class META-INF/MANIFEST.MF The output with the main class name for Recaf is: Main-Class: me.coley.recaf.Recaf Signing native libraries within the application Without signing all native libraries within an application, it will not get notarized. If your app has no native libraries, skip this step. Otherwise, you should expect a notarization error: The Apple notary service will respond with an error message indicating the presence of an unsigned library. Our sample application, Recaf, does contain a native library libjnidispatch.jnilib inside its jar file. This means we need to sign the native library file in Recaf’s jar for further notarization. Follow these steps: Extract the library from the jar file: ./jdk8u252.jdk-full-nosign/Contents/Home/bin/jar \ xf ./recaf-2.0.0-J8-jar-with-dependencies.jar \ com/sun/jna/darwin/libjnidispatch.jnilib Unlock the key chain: security unlock-keychain -p ${YOUR_PASSWORD} Create an entitlements file for signing the library (we will use the same entitlements as is used in Liberica JDK, see below). Sign the library: codesign -o runtime -s "${IDENTITY}" \ --prefix me.coley.recaf. \ --entitlements ./entitlements \ -vvvv ./com/sun/jna/darwin/libjnidispatch.jnilib Add the signed library to Recaf’s jar file: ./jdk8u252.jdk-full-nosign/Contents/Home/bin/jar \ uf recaf-2.0.0-J8-jar-with-dependencies.jar \ com/sun/jna/darwin/libjnidispatch.jnilib Now you can use the Recaf jar to create and notarize your macOS application with bundled Liberica JDK. Creating a Mac application image with bundled Liberica JDK Liberica JDK's javapackager signs the application, adds the necessary entitlements, secures timestamp and the hardened runtime while creating native macOS images. Should you need help on the options, type in the inline help command: ./jdk8u252.jdk-full-nosign/Contents/Home/bin/javapackager \ -help type - mac.app, pkg etc.> \ <-verbose> Here are all the bundler parameters for Mac Application Image (mac.app): name - App Name - String arguments - Command Line Arguments - List mac.bundle-id-signing-prefix - Bundle Signing Prefix - String classpath - Main Jar Classpath - String mac.signing-key-developer-id-app - Apple Developer ID Application Signing Key - String icon.icns - .icns Icon - File jvmOptions - JVM Options - List jvmProperties - JVM System Properties - Map mac.category - Category - String mac.CFBundleIdentifier - CFBundleIdentifier - String mac.CFBundleName - CFBundleName - String mac.CFBundleVersion - CFBundleVersion - String runtime - JRE - RelativeFileSet applicationClass - Main Class - String mainJar - Main Jar - RelativeFileSet preferencesID - Preferences ID - String userJvmOptions - User JVM Options - Map appVersion - Version – String The next step is to create a Mac native application image with the following command: ./jdk8u252.jdk-full-nosign/Contents/Home/bin/javapackager -deploy \ -native image \ -name Recaf \ -outdir res_image \ -srcfiles ./recaf-2.0.0-J8-jar-with-dependencies.jar \ -outfile recaf \ -appclass me.coley.recaf.Recaf \ -Bruntime=./jdk8u252.jdk-full-nosign/Contents/Home/ \ -BappVersion=2.0.0-liber Besides, you may pass your entitlements file using the -Bmac.entitlements=\ option. If the entitlement file is not specified, the following entitlements are used: version="1.0"> com.apple.security.cs.allow-jit com.apple.security.cs.allow-unsigned-executable-memory com.apple.security.cs.disable-executable-page-protection com.apple.security.cs.disable-library-validation com.apple.security.cs.allow-dyld-environment-variables The resulting image will be created in the res_image/bundles/Recaf.app directory. You can ensure that your application is signed and has the necessary properties/options with the command: codesign -dv --entitlements :- ./res_image/bundles/Recaf.app/ It should produce the following output: Executable=***/res_image/bundles/Recaf.app/Contents/MacOS/Recaf Identifier=me.coley.recaf Format=app bundle with Mach-O thin (x86_64) CodeDirectory v=20500 size=378 flags=0x10000(runtime) hashes=3+5 location=embedded Signature size=8998 Timestamp=*********************** Info.plist entries=15 TeamIdentifier=8LBATW8FZA Runtime Version=10.14.0 Sealed Resources version=2 rules=13 files=5 Internal requirements count=1 size=176 version="1.0"> com.apple.security.cs.allow-jit com.apple.security.cs.allow-unsigned-executable-memory com.apple.security.cs.disable-executable-page-protection com.apple.security.cs.disable-library-validation com.apple.security.cs.allow-dyld-environment-variables Given that application images are most suitable for testing and not typically used directly in distribution, we will now focus on creating DMG or PKG bundles tailored for end users. Creating a DMG bundle Choosing between DMGs and PKGs depends on your preference. While these two are perfect for end-user distribution, they possess slightly different qualities. Some developers and vendors ship software in both formats, or go for wrapping a PKG inside a DMG. DMG is effectively a disk image the end user can click on and install an application in the same way as when inserting a CD. Issue the following command to create a DMG image with javapackager: ./jdk8u252.jdk-full-nosign/Contents/Home/bin/javapackager -deploy \ -native dmg \ -name Recaf \ -outdir res_dmg \ -srcfiles ./recaf-2.0.0-J8-jar-with-dependencies.jar \ -outfile recaf \ -appclass me.coley.recaf.Recaf \ -Bruntime=./jdk8u252.jdk-full-nosign/Contents/Home/ \ -BappVersion=2.0.0-liber The ready DMG image will be placed in ./res_dmg/bundles/Recaf-2.0.0-liber.dmg. Then submit the newly made DMG bundle to Apple for a standard procedure and get it notarized. Apple provides a set of tools for notarizing bundles. The two important ones are altool (to interact with the notary service) and stapler (to work with notarization results and staple the bundle afterward). Send the DMG bundle to the Apple notary service by typing in the command: xcrun altool --notarize-app \ -primary-bundle-id com.bell-sw.recaf.0 \ -username ${USERNAME} \ -password '${YOUR_PASSWORD}' \ -file ./res_dmg/bundles/Recaf-2.0.0-liber.dmg You should get the response: 2020-04-27 08:43:58.152 altool[47259:19144203] No errors uploading 'res_dmg/bundles/Recaf-2.0.0-liber.dmg'. RequestUUID = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx At this point, you need to wait for a response from the notary service and check the notarization request status occasionally using the provided request UUID: xcrun altool --notarization-info ${RequestUUID} \ -u ${USERNAME} \ -p '${YOUR_PASSWORD}' Please note that you will have to wait a while for the notarization status. If requested right away, instead of the status, an error will show up saying the request UUID is not found. This is what you will see when the service processes your bundle: *********************** altool[47696:19145453] No errors getting notarization info. RequestUUID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx Date: *** Status: in progress LogFileURL: (null) Once the bundle is verified, you will get the ‘success’ status, and the package becomes approved: *********************** altool[88539:21766482] No errors getting notarization info. RequestUUID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx Date: *** Status: success LogFileURL: https://osxapps-ssl.itunes.apple.com/itunes-assets/********* Status Code: 0 Status Message: Package Approved By following the LogFileURL link, you will get a formatted JSON response: { "logFormatVersion": 1, "jobId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "status": "Accepted", "statusSummary": "Ready for distribution", "statusCode": 0, "archiveFilename": "Recaf-2.0.0-liber.dmg", "uploadDate": "********************", "sha256": "3d742fbfbbf6252a6d35eb23114b387934dac1c0be423b357c0b257026cd1fba", "ticketContents": [ { "path": "Recaf-2.0.0-liber.dmg", "digestAlgorithm": "SHA-256", "cdhash": "56e2fa7437a638721fed896a24880607ffca619d" }, { "path": "Recaf-2.0.0-liber.dmg/Recaf.app", "digestAlgorithm": "SHA-256", "cdhash": "8739a51515aded06148e434b735ddf7147d4821a", "arch": "x86_64" }, ... ], "issues": null } Now that the Apple notary service has approved the bundle and generated a special ticket, staple the bundle with the command: xcrun stapler staple ./res_dmg/bundles/Recaf-2.0.0-liber.dmg The output of the ticket stapling command should look like: Processing: *****/res_dmg/bundles/Recaf-2.0.0-liber.dmg Processing: *****/res_dmg/bundles/Recaf-2.0.0-liber.dmg The staple and validate actions worked! Validating the DMG bundle You can also validate your bundle separately with the command: xcrun stapler validate ./res_dmg/bundles/Recaf-2.0.0-liber.dmg This should produce the following output: Processing: *****/res_dmg/bundles/Recaf-2.0.0-liber.dmg The validate action worked! If the validation is successful, you should be able to install the application! Creating a PKG bundle As we said before, it is required to choose either a DMG or PKG file to create at this stage. You may opt for PKG as this installer format provides sophisticated means to build an application installation experience. The instructions for making a notarized PKG file using the javapackager are very similar to those for the DMG one. Type in this command to generate a PKG bundle for Recaf: ./jdk8u252.jdk-full-nosign/Contents/Home/bin/javapackager -deploy \ -native pkg \ -name Recaf \ -outdir res_pkg \ -srcfiles ./recaf-2.0.0-J8-jar-with-dependencies.jar \ -outfile recaf \ -appclass me.coley.recaf.Recaf \ -Bruntime=./jdk8u252.jdk-full-nosign/Contents/Home/ \ -BappVersion=2.0.0-liber Then you should notarize it by sending it to the Apple notary service: xcrun altool --notarize-app \ -primary-bundle-id com.bell-sw.recaf.0 \ -username ${USERNAME} \ -password '${YOUR_PASSWORD}' \ --file ./res_pkg/bundles/Recaf-2.0.0-liber.pkg The output will look like this: *********************** altool[30652:19480464] No errors uploading 'res_pkg/bundles/Recaf-2.0.0-liber.pkg'. RequestUUID = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx Wait a little and check the notarization results: xcrun altool --notarization-info ${RequestUUID} \ -u ${USERNAME} \ -p '${YOUR_PASSWORD}' Please note that it may take some time for the Apple notary service to process the request. When the notarization is complete, this command will return: *********************** altool[30708:19481150] No errors getting notarization info. RequestUUID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx Date: *********************** Status: success LogFileURL: https://osxapps-ssl.itunes.apple.com/***** Status Code: 0 Status Message: Package Approved You may follow the LogFileURL link to see the notarization details for your bundle: { "logFormatVersion": 1, "jobId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "status": "Accepted", "statusSummary": "Ready for distribution", "statusCode": 0, "archiveFilename": "Recaf-2.0.0-liber.pkg", "uploadDate": "***********************", "sha256": "cf6480a8feb54236a9e0a2894a85c130b373a173bc270e661f111e2df4b65e4c", "ticketContents": [ { "path": "Recaf-2.0.0-liber.pkg", "digestAlgorithm": "SHA-1", "cdhash": "22d41fe89a8af1bcd7e594eab94dce942f822874" }, { "path": "Recaf-2.0.0-liber.pkg/Recaf-app.pkg Contents/Payload/Applications/Recaf.app/Contents/MacOS/libpackager.dylib", "digestAlgorithm": "SHA-256", "cdhash": "81ec29cd1ed5c30b1ac0f9aca785e8389d3b0c16", "arch": "x86_64" }, ... ], "issues": null } Having done that, staple the generated ticket to the application: xcrun stapler staple ./res_pkg/bundles/Recaf-2.0.0-liber.pkg This command will produce the following output: Processing: ***/res_pkg/bundles/Recaf-2.0.0-liber.pkg Processing: ***/res_pkg/bundles/Recaf-2.0.0-liber.pkg The staple and validate actions worked! Validating the PKG bundle You can check the signature in the PKG file by clicking the lock icon in the top right corner. The popped-up dialog informs of all its details. Conclusion Notarizing an application with the Liberica JDK package and the Javapackager is a straightforward and easy process. Remember to follow the steps thoroughly and use an unsigned copy of Liberica JDK. The resulting application will be notarized and have the necessary credentials to comply with the requirements set by Apple. To get the aforementioned unsigned copy of Liberica JDK, click the button below, leave your details, and our support team will gladly help you. Request Licensing Useful links javapackager Notarizing macOS Software Before Distribution Resolving Common Notarization Issues Hardened Runtime Recaf - modern bytecode editor Col-E/Recaf: A modern Java bytecode editor
- [Liberica JDK 8 with JDK Flight Recorder support preview is available](https://bell-sw.com/announcements/2020/06/15/Liberica-JDK-8-with-JDK-Flight-Recorder/): JDK Flight Recorder (JFR) is a low-overhead data collection framework for troubleshooting Java applications and the HotSpot JVM, which produces event data flow for further analysis with Liberica Mission Control. JFR was contributed to OpenJDK by Oracle to JDK 9 and became a part of JDK 11 afterward. It was being backported to JDK 8 during the last year and became one of the most wanted features for companies that use JDK 8 in production. The backport is based on the JFR 2.0 implementation in JDK 11. New functionality as JFR streaming API available in JDK 14. To use Liberica Mission Control with JFR in JDK 8u262, you need to use the Early Access binaries for LMC 7.1.2 with version detection patch. Liberica JDK 8u262 EA for Linux x64: bellsoft-jdk8u262-ea+6-linux-amd64.deb bellsoft-jdk8u262-ea+6-linux-amd64.rpm bellsoft-jdk8u262-ea+6-linux-amd64.tar.gz bellsoft-jdk8u262-ea+6-src.tar.gz Liberica Mission Control 7.1.2 EA: bellsoft-lmc7.1.2-ea-linux-amd64.tar.gz bellsoft-lmc7.1.2-ea-macos-amd64.zip bellsoft-lmc7.1.2-ea-windows-amd64.zip
- [SoftChalk chooses Liberica JDK to create a reliable and secure educational platform based on OpenJDK](https://bell-sw.com/announcements/2020/06/16/SoftChalk-Chooses-Liberica-JDK/): By leveraging Liberica JDK, SoftChalk receives Apple notarization for its Java 8 client and gets macOS release planning back on track. BellSoft, OpenJDK distribution vendor and one of the top-5 OpenJDK contributors, today announced that SoftChalk, provider of content authoring software, has adopted Liberica JDK. With this product, the company will be able to create a fully secure foundation for its educational platform based on OpenJDK and reach macOS Catalina users. SoftChalk is the leading provider of content authoring software for educators in K12 school districts, colleges and universities. For the past 15 years, educators in more than 1500 educational institutions have used SoftChalk to develop interactive and engaging online lessons for their students. The company’s Mac Java client is based on Java 8. Its weaker libraries left them unable to pass Apple notarization and keep releasing their product for the growing portion of customers on macOS Catalina. Therefore, SoftChalk had to defer multiple releases in a row. They needed an OpenJDK distribution that would support Java 8 and hold Apple notarization. Otherwise, they would have to go through a painful update of the whole JDK to a compliant version. The process would further postpone the release schedule and put the business on hold. SoftChalk adopted Liberica JDK distribution because it integrated with their legacy product line seamlessly and included long-term support with timely security fixes. One of the core decisive factors was that Liberica JDK was scanned for malicious components and code-signing issues and got notarized by Apple on time. This way, SoftChalk received a fully compliant Java 8 foundation for their product and were back on track with the release planning even sooner than they expected. “The foundation’s reliability, control of all vulnerable points and a quick resolution of problems by high-level professionals are the only things that allow companies and their customers to be safe. That is why we are happy that such a large platform as SoftChalk is now based on Liberica JDK. Indeed, today, as never before, stability and safety are important to us. We want to be the ones who make the online world more reliable than offline,” explains Alex Belokrylov. “Inability to get our Java 8 client certified by Apple was a show-stopper, as we were unable to deliver our product to the growing portion of users on macOS Catalina. BellSoft’s quick response and ability to receive Apple notarization helped us release much sooner than we thought we could. We were impressed with the support BellSoft offers and we could not find another distribution that would integrate so painlessly with our product line as Liberica JDK,” says Chuck Woods, Manager, Engineering and Operations at SoftChalk. About BellSoft BellSoft releases and supports Liberica JDK, an OpenJDK binary distribution verified by TCK for Java SE Standard Compliance. BellSoft engineers have been contributing to the OpenJDK project since its inception. Being one of the top-5 OpenJDK contributors, BellSoft drives a community-powered approach to deliver reliable and compact containers with Liberica JDK targets microservice solutions on. Using popular as well as in-house performance optimization techniques and tools allows BellSoft to work on performance tuning for industry leaders. Website: https://bell-sw.com About SoftChalk SoftChalk offers content authoring and hosting solutions that are critical to the success of eLearning initiatives, providing educators with easy, fast, affordable ways to create, manage and share rich, interactive content that engages students in the learning process and inspires learning. Lesson content is web-based and can be delivered in almost any learning management system, content management system, on a web-server, on mobile devices or in the cloud — anywhere for just-in-time learning. Website: https://softchalk.com/
- [JDK Flight Recorder – a gem hidden in OpenJDK](https://bell-sw.com/announcements/2020/06/24/Java-Flight-Recorder-a-gem-hidden-in-OpenJDK/): JDK Flight Recorder, also Java Flight Recorder (JFR) is a mighty powerful feature of Hotspot JVM, although it is not as widely known. JFR is not a novel technology. It takes its origin from JRockit JVM; later, it was ported to Hotspot JVM and introduced in Oracle Java 7. At the time of Java 9, JFR was fully open source, and now it is an integral part of OpenJDK. In this article, I would like to highlight key features of Java Flight Recorder and how it can be useful for Java developers. What is a Java Flight Recorder? Java Flight Recorder is a performance/diagnostic tool that could be a huge time saver for Java engineers while troubleshooting applications at runtime. As the name suggests, Java Flight Recorder collects various kinds of events from JVM runtime and records them in the form of binary log files (further referred to as recordings). In OpenJDK 11, JFR is capable of tracing a few hundred types of events creating a comprehensive JVM runtime picture. Recordings produced by JFR are self-contained files that could be further analyzed in Mission Control. What is Mission Control? Mission Control is essentially a graphical front end working with JFR recordings (binary log files) produced by Java Flight Recorder. Mission Control is also open source, and its source code is hosted on GitHub. JFR recordings are logically straightforward, each one a plain collection of events. For them to make sense, these data need to be post-processed. Mission Control offers several built-in reports outlining various aspects of JVM runtime (e.g., code execution, object allocation, garbage collection, and more). Besides general reports, Mission Control has a number of rule-based heuristics for detecting typical problems of Java applications. Finally, you can use a generic event browser and customizable filters to configure reports tailored for your task. Is Java Flight Recorder just another Java profiler? To a certain extent, features of JFR + Mission Control compete with ones of profilers, this is true. Though comparing it to traditional profilers is not very fair. JFR is a tool of its own kind. It is also a feature of JDK, so other Java profilers besides Mission Control could benefit from JFR capabilities too. For certain profiling tasks (e.g., identifying code hot spots or object allocation profiling), JFR + Mission Control is similar to a typical Java profiler (Visual VM, JProfiler, YourKit profiler). These are good open source Java profilers that you can use to avoid paying for commercial tools. Some features essential for profilers are not covered by JFR. Say, if you want to trace SQL statements being sent to the database, JFR is not your tool. You can send application-specific events to JFR. This is a compelling way to collect telemetry peculiar to your application, though it requires upfront work to place event generation in application logic. Finally, some kinds of JVM telemetry are available only via JFR. How can I start using Java Flight Recorder? JFR is an integral part of OpenJDK now. You do not need to download anything besides JDK or configure JVM in any special way to start using it. Open JDK 11 is the earliest LTS (Long Term Support) release with integrated JFR. If you do not already have OpenJDK with JFR support, you can get a version matching your OS and CPU architecture from Liberica JDK. Liberica JDK is a free and open source binary distribution with High-Powered support from BellSoft, a leading OpenJDK contributor. Liberica JDK supports one of the widest range of platforms, which makes it a Unified Java Runtime for cloud, server, and desktop use. JFR workflow is as follows: start flight recording session with a particular configuration, once the session is active for some time, dump recording to a file, stop the session after the dump (this step is optional, you can leave it active if you plan to take more dumps later). There are three ways to start a flight recording session: Using the jcmd CLI tool included in JDK, start/stop/dump JFR sessions. You would need shell access to the server running your Java application to use jcmd. Using Mission Control, you can do the same for both locally running and remote processes (you would need JMX configured to connect to JVM remotely). It is also possible to activate a JFR session on startup via JVM command-line options (this is very useful if you want to profile applications during startup). Starting a flight recording session also requires a configuration (called a profile). The profile defines which kind of events would be collected during the session. It also determines per probe configuration (frequency for method sampling, duration threshold for I/O events, etc.). There are two built-in profiles, “default” and “profiling.” You can also configure and use custom profiles via Mission Control UI. Once you have a JFR recording file, you would need Mission Control to open it. Mission Control is distributed separately from OpenJDK. I would recommend getting Liberica Mission Control at this stage. A brief overview of Mission Control reports Mission Control has tons of features and a fairly steep learning curve. Oracle’s Java 8 had pre-open source Mission Control 5.5 bundled with JDK. Mission Control 6 was bundled with JDK 9, and it was a dramatic change partially related to open-sourcing of the codebase. This same version, Mission Control 6, introduced automatic anomaly detection. Hints produced by built-in heuristics could be very practical for beginners, but sooner or later you would need to dive into other reporting screens. Mission Control 8 is the latest version, it can analyze recordings from JDK 7+. To launch JMC, you need JDK 11+. Here I would like to give a brief description of key reports available in Mission Control. Method profiling JFR can periodically capture stack traces for running threads in JVM. This technique is known as sampling profiling and available in all Java profilers. Statistical analysis of a large corpus of stack traces gives an excellent picture of where the application spends most of the time down to method name or even line number. This report provides useful visualization for this subset of data collected by JFR. This method is typically used in cases where the application is CPU bound. Sampling helps to identify code that is inefficient or could be optimized for better performance gain. Memory This report outlines a memory usage story of your Java application. It combines multiple data points, with the most important of them being object allocation samples. If enabled, JFR records samples of new object allocation events, including class being allocated, size of object, and stack trace to the point of allocation. Statistical analysis of these samples could reveal a lot about your code. “Memory” reports show top classes of objects being allocated. Information on actual allocated size, including stack trace to the point of allocation, is also available. This report is most helpful if you want to reduce garbage collector pressure or optimize memory usage. Memory profiling is extremely valuable when it quickly identifies the “littering” code in your application and provides ideas for optimization. Lock Instances Contentions and deadlocks is another class of Java problems hard to approach without a good tool. JFR collects events of thread blocking on synchronized sections and other concurrency primitives that can be useful during the investigation of Java concurrency problems. “Lock Instances” report would show you aggregated statistics for these events. “Threads” is another report helpful in tracking down concurrency issues. A built-in “Profiling” configuration has a threshold on the duration for such events. Keep in mind that you may need to lower it to get a more detailed picture. The cost of inter-thread contention becomes a harsh reality for high parallel code running on dozens of cores. Java ships with a few concurrency primitives, including lock-free ones. The contention report helps understand the cost of multithreading in your application and possibly highlights bottlenecks where switching to more elaborate locking is most beneficial. File I/O and Socket I/O Blocking file and socket I/O operations are also traced by JFR. These reports show aggregated statistics built from file and socket I/O events, respectively. It is exhilarating to see which files and how long your application has been reading or at which remote endpoints it has exchanged data. There is a little caveat, though. If using JFR for the first time, you are likely to find both of these reports to be empty or almost empty. The reason is the 10 ms threshold used by default for these kinds of events. If you want to see a full I/O picture, set this threshold to 0 when starting a flight recording session. This report is very good at catching “unexpected” I/O events buried deep inside of layers of libraries and frameworks. Threads In this report, you can see various events (contention related, I/O associated, Java thread state changes) put into a unified timeline. Here you can see the interaction of threads in an intuitive visual way. The ability to zoom into the millisecond level of precision is helpful too. Exceptions Every Java application is producing a handful of exceptions at runtime. Some of them end up in log files, but many are suppressed for one reason or another. Silently swallowed exceptions sometimes lead to many hours wasted during the incident investigation afterward. JFR sees all exceptions (whether they were suppressed or not). There are two kinds of events related. JFR records the total count of exceptions produced by runtime. This metric helps to identify exception misuse. JFR could also record every single exception created, including the stack trace, though you would need to tweak the flight recording session’s configuration to get this level of detail. Garbage Collections GC troubleshooting typically involves digging through GC logs. And of course, you have your GC logging configured properly in your production, don’t you? JFR collects detailed information concerning GC events, and this report visualizes this data. Almost any metric of GC present in JVM is available here. If you need to tune GC or troubleshoot abnormal GC pauses, this is a report to start with. VM Operations HotSpot has a concept of VM operations ranging in types. But the critical point is that all of them require the infamous Stop-the-World pause. With some JVM options, you can enable logging of VM operation or use JFR and this handy report. VM operations are often mistaken for GC pauses (which is just one type of VM operations). People tend to blame Stop-the-World on garbage collectors. This report is your first stop if the application is experiencing Stop-the-World pauses. Configuration and environment A neat thing about JFR files is that you can send them for analysis to an expert in the JVM domain. Files include JVM configuration, hardware configuration, and even a list of other processes running on a server and competing for resources without application. As a JVM guru, I can get almost any context information I need from the recording file itself. Continuous flight recording JFR + Mission Control could be used as a free and capable Java profiler, no doubt. Though using it in continuous mode could bring even more value. To get the full benefit from continuous flight recording, you would need to configure your JVM to be accessible via JMX add JVM command-line arguments to activate flight recorder on startup A minimal command to start flight recording session on startup is below -XX:StartFlightRecording=name=background,maxsize=100m An important option is “maxsize,” which limits how many events would be retained in memory. Now you have JFR silently collecting events in the background. As long as the application runs fine, you can forget about JFR being active. The default JFR profile adds very little overhead. Still, in an unfortunate turn of events, you can quickly connect to JVM via Mission Control and dump accumulated events from a problematic server, which is especially useful for tracking down problems reproducing only on live production. One can argue that instead of pulling data dumps from individual VMs, it is better to have a centralized monitoring system where you can access diagnostics from any server. In theory, that would be incredible, but actually arranging quality centralized monitoring is extremely difficult. Central monitoring solutions have to compromise on the level of details. Continuous flight recording and the ability to pull event dumps on demand are complementary to central logging and general monitoring solutions, as you can get very detailed telemetry from JVM. Plus, it is already built into your OpenJDK, so why not start using it? Java 8 roadblock While Java 14 is already out, many systems are stuck with Java 8, whatever the reason. Unfortunately, Java Flight Recorder is not included in OpenJDK 8 (it is available in Oracle Java but not for free). Java version dependency stands behind the low Java Flight Recorder adoption among developers. You need to move to Java 11 to be able to use JFR in OpenJDK. The good news is that Java Flight Recorder has been backported to OpenJDK 8 codebase and will soon be ready to use. Conclusion Java Flight Recorder is a mature technology integrated with modern OpenJDK. JFR + Mission Control offers a reliable alternative to commercial Java profilers for most common profiling tasks. But remember that you can also use open source Java profilers. JFR goes beyond simple profiler workflow with its ability to continuously collect data and access them remotely. As part of the JVM binary, Java Flight Recorder could be easily enabled and does not require any upfront configuration, making it a powerful tool in the Java engineer’s toolbox. So, if you have problems with your Java performance or stability, Java Flight Recorder and Mission Control is a combo you definitely need to give a try. Related Posts Hunting down code hotspots with JDK Flight Recorder Hunting down memory issues with JDK Flight Recorder JVM in Linux containers, surviving the isolation JDK Flight Recorder, The Programmatic Way
- [Liberica JDK 8u262,
11.0.8, 14.0.2 builds
are released](https://bell-sw.com/announcements/2020/07/14/The-Liberica-8u262-11_0_8-14_0_2-are-generally-available/): Today we are announcing the general availability of Liberica JDK’s three brand-new versions: 8u262, 11.0.8, and 14.0.2. These three releases contain, in total, 765 backports of fixes that address known functional issues (six of them are the backports of BellSoft commits) and incorporate 11 common security vulnerabilities and exposures. In addition to these enhancements, the releases are packed with the following exciting new features from the BellSoft team: 1. Alpine Linux musl support added for Liberica JDK 8. Previously, support for musl-based Alpine Linux was only available on Liberica JDK 11 and 14. We added a backport for JDK 8 with this release, as there are so many companies and developers using JDK 8 in production and eager to leverage Alpine Linux for its resource efficiency and simplicity. This feature will enable users to trim down Docker containers’ size and keep their environment as small and efficient as possible. 2. Javapackager added for Liberica JDK 8 and 11. A feature in OpenJFX, which is now being re-engineered by the OpenJDK community as the jdk.incubator.jpackage module, was made available for Liberica JDK 8 and 11. It allows installing and uninstalling Java applications in a manner that is suitable for a specific platform (whether they are msi and exe formats on Windows, deb and rpm on Linux, or pkg and dmg on macOS), and distributing them in a user-friendly way. With Javapackager being added, our users can now build application images, bundle them with the runtime as a single package, and reduce the installation requirements. 3. Liberica JDK 8 with JDK Flight Recorder is available. JDK Flight Recorder (JFR) is a low-overhead JVM metrics collection framework for troubleshooting Java applications and the HotSpot JVM. This performance/diagnostic tool can be a huge time saver for Java engineers who need insights into how their applications perform while troubleshooting them at runtime. Being backported to OpenJDK 8 during the last year, it surely became one of the most wanted features. The backport will become generally available in autumn, but we decided to create a preview of this release in July for the early birds! To use Liberica Mission Control with JFR in JDK 8u262, you need to use the Early Access binaries for LMC 7.1.2 with version detection patch JMC-6554 available here: bellsoft-lmc7.1.2-ea-linux-amd64.tar.gz bellsoft-lmc7.1.2-ea-macos-amd64.zip bellsoft-lmc7.1.2-ea-windows-amd64.zip and the Liberica JDK binaries from here: bellsoft-jdk8u262+10-windows-amd64-jfr.msi bellsoft-jdk8u262+10-windows-amd64-jfr.zip bellsoft-jdk8u262+10-windows-amd64-full-jfr.msi bellsoft-jdk8u262+10-windows-amd64-full-jfr.zip bellsoft-jdk8u262+10-linux-amd64-jfr.deb bellsoft-jdk8u262+10-linux-amd64-jfr.rpm bellsoft-jdk8u262+10-linux-amd64-jfr.tar.gz bellsoft-jdk8u262+10-linux-amd64-full-jfr.deb bellsoft-jdk8u262+10-linux-amd64-full-jfr.rpm bellsoft-jdk8u262+10-linux-amd64-full-jfr.tar.gz 4. Liberica 11 for AArch64 now contains the Full bundle that includes Liberica FX. In the previous versions, AArch64 support for LibericaFX was limited to Liberica 14. Starting from the 11.0.8 release, Liberica 11 for AArch64 now contains the Full bundle, including Liberica FX. This way, users that are programming on JDK 11 can now launch JavaFX applications on the AArch64-based OSes on SBCs like Raspberry Pi and stay on the LTS version of Liberica. With these new releases, we deliver upon our commitment to enabling more performance with less hassle and administration. Hope you will enjoy them! All builds are ready for download here.
- [Optimizing Java Microservices with the smallest container on the market](https://bell-sw.com/announcements/2020/07/14/Optimizing-Java-Microservices-with-the-smallest-container-on-the-market/): BellSoft prepares the final changes to upstream the Alpine Linux port to OpenJDK We have some exciting news! BellSoft’s new JEP 386 is designed to integrate the Portola Project into upstream OpenJDK. This port will allow building a variant of the JDK on Alpine Linux/x64 and other Linux distributions that use musl as their primary C library, so there is no need for the glibc portability layer. The company’s engineering team created a merge from the Portola repository to the JDK mainline branch, fixed several solutions proposed by previous contributors, and thoroughly tested the resulting patch. Alpine is a Linux distribution based on musl as libc and Busybox as a toolset. The greatest advantage of this system is its lightweight nature. Alpine is perfect for container deployments Various sources glorified Alpine Linux as “the new default OS” and “distribution of choice” when it replaced Ubuntu as the base image for Docker in 20161. Thanks to its small image size, this distribution is widely adopted in cloud deployments, microservices, and container environments. For instance, Liberica JDK Alpine musl image is at least 200 MB less than the others available (43.5 MB vs. 228 MB for Debian or even 319 MB for CentOS). With a truly minuscule Docker base image of no more than 6 MB and an embedded Docker build process, it is perfect for minimizing valuable cloud resources. Another positive feature is that the service management subsystem is not even included by default in basic Alpine Linux containers. As a result, it frees up RAM, simplifies diagnostics and system configuration and boosts launch time. At a certain angle, having less contents in a distribution reduces a possible attack surface. Maximizing business value with containers: less is more But what lies behind the need for small images in development? A simple answer is “cost efficiency”. A longer one starts with an understanding that containers are gaining traction each year. Users want to get more value while spending less, that is the gospel truth. And here we can see the absolute superiority of containerization over virtualization. Docker containers are great for streamlining development with the microservice architecture. If you plan to move from Java monolith or create a new solution based on Java microservices, count on our engineers to pave the most efficient migration path. We are offering an entirely free consultation with a BellSoft expert. Fill out the simple form below and schedule a meeting now! Request Licensing First, bug fixes and changes are delivered to the whole application package continuously and fast. Deployment may include many new instances, for example, when the application is dynamically scaled in some region. A major problem that usually arises during this process is high traffic expenditures. Also, the less the image you have to upload, the less time you waste before the application begins to perform business tasks. Second is the age-old problem of storage space. In the case of containers, an isolated environment requires tens of megabytes. In contrast, a virtual volume with full OS will be sized in gigabytes. Just imagine what your company can do with the saved space and money not spent on extra cloud storage, let alone boosted overall performance. Third, waiting for deployment rollout to finish typically takes up huge chunks of programmer’s work. It is easy to calculate profits if this time is reduced to a minimum with containers deployed almost in an instant. Let’s turn to a hypothetical enterprise that transfers to containerization. Reducing overhead charges, no longer having to set up a hypervisor and run a virtual machine… All good. New shiny containers deploy on a clean target device. Why wouldn’t the company want to save even more with smaller images? Which brings it to an idea of JDK containers on an intrinsically lightweight Alpine Linux. Before unraveling the history behind the JDK port to Alpine, we want to say that BellSoft’s binaries (the smallest in the industry!) are supported not only on Linux but on all known platforms. JEP’s background, demand and idea Mikael Vidstedt, now Director of JVM Software Engineering at Oracle, was the first to consider bringing support for alternative implementations of standard C libraries to OpenJDK. The problem was self-evident: say, a person trying to set up a Java VM on an OpenWrt router would soon find it impossible. Mr. Vidstedt decided to change the status quo by starting a venture dubbed the Portola Project in February 2017. He could easily be the one to propose the port to the master branch. But his team had neither sufficiently analyzed the changes (to define their purpose and check other possible solutions) nor done enough testing on Alpine Linux and then all available platforms: Linux, macOS X, Windows, arm, and aarch64, as the patch would affect shared code in JDK. The work on this JEP started a while back when BellSoft understood that containerized JDK 9 is quite favored among users. We supported our Liberica distribution on various platforms and made delivery convenient with a choice of channels: you could download a binary build from the website, install a repository for Linux, download an installer, find publicly available builds on package managers (like SDKMAN), or pick a container image. BellSoft saw the big future in supporting such builds in commercial terms and first created containers for Alpine Linux with glibc compatibility layer. The earliest Liberica version containing Alpine Docker images was 9.0.1 on armhf (for Raspberry Pi). The Portola Project started at times of JDK 10. Portola is about building OpenJDK linked directly to musl. In a musl-based system without the compatibility layer, programs linked to glibc (including regular JDK builds) won’t start. Such builds with a different standard library become some sort of a port. We also should take into account all other peculiarities of target systems, some covered by the existing tests and some not. New containers with OpenJDK took off fast. Liberica OpenJDK statistics on Docker Hub really shows how popular Alpine images are: 100K+ downloads and 10-star rating. The two reasons are that regular JVM images do not work on Linux and that Alpine is inherently lightweight. Given the success of Liberica containers with glibc, we, engineers at BellSoft, decided to take the “intermediary” out of the equation and enter the Portola Project to help produce a rounded and single-purpose JDK for Alpine. In the team’s view, the procedures inside the kit should address the standard library without emulating glibc behavior. We have been shipping Liberica-openjdk-alpine-musl container images since JDK 11. By getting rid of this layer, a new container was expected to save approximately 24 MB—an obvious driver for a company that strives to minimize costs for clients. BellSoft’s contribution to the Portola port Reasonable questions here would be: Why did the JDK not work with musl libc? What changes has BellSoft had to make to its Alpine Linux containers? First, the team prepared a merge from the Portola repository to the JDK master branch, which due to the branch’s ever evolving nature needed to be synchronized non-stop before it was approved and pushed. The patch was split into categories, each one examined and code-named by changes it provides. Some are minor improvements, like defining libc in config: Before: ldd_version=`ldd --version 2>&1 | head -1 | cut -f1 -d' '` After: libc_vendor=`ldd --version 2>&1 | sed -n '1s/.*\(musl\).*/\1/p’` Note: with this improvement, it is also possible to cross-compile the Linux-musl OpenJDK images on traditional Linux x64 based systems using GNU/Linux distributions. Portola cross-compilation: Clone hg.openjdk.java.net/jdk/jdk repository hg clone https://hg.openjdk.java.net/jdk/jdk Apply Portola patch Build jdk for the current platform which will be used as boot and build jdk for cross-compilation mkdir -p build/linux-x86_64-boot-release cd build/linux-x86_64-boot-release sh ../../configure --with-boot-jdk=/jdk-14.0.1 make jdk-image Download cross musl-based toolchain from https://musl.cc Copy sysroot from target system (for example from Alpine Lunux docker image) Configure musl build with devkit and sysroot export DEVKIT=/x86_64-linux-musl-cross export SYSROOT=/alpine3.8-sysroot export TARGET=x86_64-linux-musl export BOOT_JDK=build/linux-x86_64-boot-release/images/jdk sh ./configure --with-jvm-variants=server \ --with-boot-jdk=$BOOT_JDK \ --with-build-jdk=$BOOT_JDK \ --openjdk-target=x86_64-unknown-linux-musl \ --with-devkit=${DEVKIT} \ --with-sysroot=${SYSROOT} Build jdk image cd build/linux-x86_64-server-release make jdk-image Or, instead of static int sigWakeup = (__SIGRTMAX - 2), using #define INTERRUPT_SIGNAL (SIGRTMAX - 2) where SIGRTMAX is a function in musl libc. Others encouraged BellSoft to search for different solutions. One example here is that the musl library does not implement dlvsym since it is not a part of POSIX. The initial Portola Project tries to load dlvsym dynamically and provides fall back in case it is absent. The engineers decided to replace it with dlvsym stub and rewrite the code to: #ifdef MUSL_LIBC static void *dlvsym(void *handle, const char *symbol, const char *version) { return NULL; } #endif Certain solutions did not make the final cut just yet. Such as execvp in musl libc that improperly executes shell files without a valid shebang line—a known issue, actually2. Finally, the team singled out parts of code to submit them for a standalone review and fix independently of the Portola patch. A major and the most labor-intensive part was jpackage, which could even be considered a subproject of sorts. Under particular circumstances, java launcher executor re-executed the application. This is a corner case for musl libc which, when loading shared libraries, does not check them by short name. It was found that the issue with jpackage application re-execution is not specific to Alpine Linux and can be reproduced on other Linux systems that change LD_LIBRARY_PATH3. The other huge chunk of work was testing. BellSoft’s first step was to run JCK, jtreg, vmTestbase and jvmTestbase. The team got cornered a little: we tested the master repository constantly under heavy development. Fails occurred even without the Portola patch. The decision was to run all tests with JDK master branch and use the passed ones as a reference to the Portola build configured for fastdebug. As a result, some fixes were made to the patch before the merge, some already in the merge, and some were reported as musl libc bugs (like the execvp issue discussed above). To recap, by porting the JDK to musl-based Linux distributions, developers will find updating base layers and storing them in a private repository much simpler. Among other benefits are reduced time spent on deployment to a new host, shortened redeploy in general, and saved storage. All these are critical in facilitating business processes and cutting down costs, especially when a business pays for CPU consumed in the cloud. Applying jlink to reduce the size of the Java runtime, a user will be able to create an even smaller image targeted to run a specific application. But sometimes it looks too complex because of an extra step in the deployment pipeline or because of dynamic dependencies. So our alpine-musl docker images can be used in the ‘docker build’ process with extra options. For instance, a user can easily get a runtime that contains either only java.base (to run simplest apps), or some typical modules for a web application, or all the modules. The preparation process is much simpler than the full-fledged jlink tool. A user has no need to recalculate actual dependencies and just chooses between the three lightweight JDK 14.0.1 presets: Base (43.5 MB) / Light (111 MB) / Full (192 MB). The JEP will further support Java runtime not only on Alpine but on all Linux systems with musl. We claim (although some additional testing is still to come) that the JDK port runs on OpenWrt, a popular Linux distribution also based on musl. It powers many small smart devices such as routers. BellSoft guarantees the quality of its Alpine images. The company assures every container image is TCK verified. It is sometimes not so simple to combine things. For example, in some older versions of Alpine, JVM simply shut down on launch because of experimental security patches included. Containers with Liberica JDK, properly tested and verified, show none of these problems. The team is focusing major engineering efforts on this patch, supports the builds, resolves all problems in the timeliest manner, and plans to further follow musl-related part of OpenJDK. Expectations and prospects Overall, the Portola Project is moving at its designated pace towards finalization. However, to become part of the upcoming JDK 16 next year, it still should be refactored and polished. Late June saw BellSoft taking a refreshed JEP implementation for review to fix certain non-musl-related issues. There were several new patches (that are concerned with running jpackage; on AIX they are checked by SAP) integrated into the main branch. After jpackage change was tested by SAP, now, at the time of writing, it is waiting for a public review on core-libs-dev3. Portola polishing changes passed from internal review to public discussion on portola-dev4, where developers already responded to some concerns. Next is to introduce the first change into the mainline JDK, then integrate polishing changes into the Portola repository. The final touch is a public review of all Portola changes going into the JDK mainline branch. BellSoft expects the whole JEP to get processed by mid-August. We see exciting benefits and stability in Portola-powered builds. At the same time, there is a strong demand, as always, for JDK 8. One path for improvement is to build JDK 8 for Linux-musl. The proposed changes apply to the JDK most beloved by the developers (aka “the best release”) since JDK 8u had lots of containerization-related features backported5. It would still be labor- and time-intensive as the current Portola status is downstream. After the JEP is fully integrated, we might find resources to provide this option for the latest updates of version 8. What is next for BellSoft in this area? Our team wants to tackle musl libc for GraalVM within the Graal Project. This library and issues it raises for Java runtime developers are a nice spot for the long-term. Whereas previously the company poured resources into maintaining correctness in new versions of their product, now it is ready to produce novel technical features and support containers. Virtual machines are no longer the talk of the JDK community. Working with containers is seen as mainstream and the primary way to use JavaTM in production. This year and beyond, we promise to dive deeper into the topic and make the developers’ job easier and more enjoyable than it has ever been. And BellSoft likes to keep promises: We are proud to announce that the latest Liberica JDK’s versions, 8u262, 11.0.8 and 14.0.2, include the long-awaited Alpine Linux musl libc support and lots of other exciting features. Download Liberica JDK Thanks to the profound knowledge of this OS, we have created the smallest known containers among OpenJDK contributors. Our customers have used Liberica JDK containers with Alpine musl in production environment for years and admitted savings on cloud traffic, storage and even engineering time. If you want to start working in the cloud and be most efficient, if you are ready to save precious time and money, contact BellSoft right away! Request Licensing Links: Review: Alpine Linux is made for Docker https://www.openwall.com/lists/musl/2020/02/12/9 https://mail.openjdk.java.net/pipermail/core-libs-dev/2020-June/067456.html https://mail.openjdk.java.net/pipermail/portola-dev/2020-June/000441.html GeeCON 2019: Dmitry Chuyko - Do not put all eggs in one container
- [Hunting down code hotspots
with JDK Flight Recorder](https://bell-sw.com/announcements/2020/07/22/Hunting-down-code-hotspots-with-JDK-Flight-Recorder/): Hunting down code hotspots is probably the most common task for Java profilers. JDK Flight Recorder (JFR) and Mission Control (MC) are free and open source performance/profiling products available with OpenJDK 11. They have a few powerful tools for code execution profiling. In this article, I would like to walk you through the code profiling features they offer. Table of Contents What is a code hotspot? Starting profiling with Mission Control Working with Local JVM Working with remote JVM Before starting the profiler Mission Control JMX Console and Thread monitor What if JMX is not an option? Starting JDK Flight Recorder session Making JFR recording without Mission Control Method profiling report Power of “Stack Trace” view Why CPU usage is important Retrospective thread CPU usage in Mission Control Limit scope to a subset of threads What do I see in the “Stack Trace” view of the “Threads” report? Caveats of JFR method profiling “Cold” code hotspots File I/O report Socket I/O report Lock instances report Threads report Conclusions Related Posts What is a code hotspot? Usually, you are starting a profiler when there is a performance problem, or you want to optimize code to meet specific performance goals. “Performance” is most commonly expressed as execution time (time needed to execute operation) or throughput (number of operations executed per time unit). If you want to reduce the time spent on a request, it is obvious to focus on code that takes the longest to run. This is what we call “hot code” or “code hotspots.” Profilers are great tools for identifying “code hotspots” and JFR + MC is an excellent profiler combo. Let’s launch the application and start profiling, right? Starting profiling with Mission Control We will work with OpenJDK 11 and Mission Control 7.1, JDK Flight Recorder is integrated into OpenJDK 11. You can get the distribution of OpenJDK and Mission Control for your OS from Liberica if you don’t have them already. If you stick with Java 8, Flight Recorder support is available with OpenJDK 8u262. You can find more details in this post by BellSoft. You can use mission control with either a locally running JVM or a remote one. In the latter case, you need JMX port configured on a remote JVM. Of course, you would need an application you will profile and some load to keep it busy. Working with Local JVM Locally running JVM processes are listed in the “JVM Browser” view. You need to identify the JVM you want to profile. Besides, you can either open the JMX console for that JVM or control flight recorder. Working with remote JVM With OpenJDK, Flight Recorder is available remotely via JMX. As might be expected, you would need a JMX socket configured on JVM that you would like to profile. There are two ways to make the JMX socket available: JMX could be enabled via JVM command-line arguments. You can find the required configuration options in the official documentation. Using the jcmd command, you can start the JMX socket on JVM with no up-front configuration and no restart necessary. Below is an example of a command to start JMX with minimal configuration, you would need to know the PID of a process and be logged under the same user as the target JVM. jcmd PID ManagementAgent.start \ jmxremote.authenticate=false \ jmxremote.ssl=false \ jmxremote.port=5555 Once the command is executed, you can connect to your JVM via port 5555 using the instruction in the following pages. If the JVM you want to profile is behind NAT/firewall (e.g., it is running within Kubernetes), you may need to use port forwarding and additional configuration tweaks to make JMX work. Having configured the JMX socket, you need to add remote JVM to it in the “JVM Browser” view. Click the button at the toolbar of that view. You will be prompted to enter JMX connection details. Once you finish JMX configuration, nodes for your remote JVM would appear in the view. Now you can see either a JMX Console or control flight recorder. Before starting the profiler Before starting profiling and focusing on code, it would be nice to get an overall CPU usage picture on the host where your JVM is running. Is the CPU overutilized? Do other processes compete for CPU resources? How much CPU is the JVM process consuming? Which Java threads account for the highest CPU usage? Answers to these questions will help you to choose the right reports in Mission Control later. These questions could be resolved with standard system tools such as top and pidstats, but it is also possible to use JMX console in Mission Control. Mission Control JMX Console and Thread monitor Before starting Flight Recorder, I always recommend looking at the JMX console built into Mission Control. With JMX console, you can both monitor the system and process CPU usage. These metrics are available at the overview tab. You can observe CPU usage per thread as well, which is quite beneficial information for code execution profiling. You need to go to the “Threads” tab and check the “CPU Profiling” box above the table of threads. After this, you could see values in the “Total CPU Usage” column. Pay attention, though, that Mission Control shows the percent of CPU usage from all cores available on the host. As a single thread cannot consume more than a single core, in that table, 100% / N (where N is the number of cores) is the max value you can see. What if JMX is not an option? What if you have console access to JVM, but no way to connect to it via JMX? You can still record JFR files using jcmd, then copy them to your machine and open in Mission Control. I will elaborate on this scenario later in this article. Obviously, the JMX Console provided by Mission Control would not be useful in this case, but you can use console tools such as pidstat or sjk to monitor per-thread CPU usage. Starting JDK Flight Recorder session Let’s start the Flight Recorder now. Right click on the “Flight Recorder” node in the tree on the “JVM Browser” view and select the “Start Flight Recording …” option. You will see a dialog with options to start the Flight Recorder session. This screenshot reflects starting Flight Recorder for 2 minutes, with “Profiling - on server” default settings. Now you can hit the “Next” button and get another dialog allowing you to tweak the most common Flight Recorder options for the next recording session. Some of these options would affect the reports explained in this article: “Method Sampling” — this option controls the frequency of stack trace sampling. “Maximum” means 100 samples per second, which is a good trade-off between performance and data quality. “Exceptions” — you can choose whenever to record all Throwables or just Errors. Recording all Throwables may be expensive, but may be useful to pinpoint excessive exception usage in application. Thresholds “Synchronization”, “File I/O”, “Socket I/O” — for detailed analysis of “cold” hotspots, I would recommend you set these thresholds to zero. Though it may increase overhead significantly, so do it only if you really need this information. Now let’s hit “Finish” to start the Flight Recording session. You can click “Next” again, although, in that case, you would have a chance to view/edit Flight Recorder configuration at a lower level. After starting the Flight Recorder session, Mission Control would wait for the specified duration, then stop Flight Recorder, dump the data file, open it and present several reports. Making JFR recording without Mission Control JFR recording could be produced without Mission Control, too. You can use jcmd to create JFR files, then copy them to desktop and open In Mission Control. Here is a quick instruction to capture JFR recording. You would need to know the PID of the target JVM and execute jcmd under the same user account. Start JFR recording with command below jcmd JFR.start settings=profile The result of this command should be something like what we have below. You would need to remember the number of the recording for later. Started recording 1. No limit (duration/maxsize/maxage) in use. Use JFR.dump recording=1 filename=FILEPATH to copy recording data to file. Wait a little for data to collect Use the command suggested in step 1 to dump JFR data jcmd JFR.dump recording= filename= This action would create a JFR file. Stop the recording session jcmd JFR.stop recording= Now that you have a JFR file, you can open in Mission Control later. Method profiling report As you may remember from the previous article, Flight Recorder collects various types of events and Mission Control builds a number of reports from that data. We want to identify areas of code that have contributed most to the execution time of our request. The usual profiling method for such tasks is a stack trace sampling. The idea is simple. During execution at regular intervals, Flight Recorder is recording trace for each thread. Taking samples from a single thread and observing that method X is present on a stack in 50% of the sample, you can assume that the sum of all method X invocation time is 50% of profiling session time. While the idea is simple, well, in practice, you have to deal with multiple threads and non-ideal sample distribution. Still, this approach remains extremely useful in practice. Let’s open the “Method profiling” report. The report itself is pretty barebone, and if you have used a different Java profiler before, it feels lacking details. They are hidden in a “Stack Trace” view (panel below). Power of “Stack Trace” view Stack view allows visualizing a set of stack traces as a tree. How do stack traces become a tree? We all know how Java stack traces look like (be it exception stack trace or stack trace from thread dump). Data produced by sampling profiling is just a bunch of stack traces. Here are steps on how this data is transformed into a tree. Convert each stack trace into a string by concatenating frames. Now that you have a multiset of strings, calculate each one’s occurrences and compile a histogram. You’ve got a histogram, a table with two columns “trace” and “count.” If you sorted this table and group by common prefix, it would become a tree. In the context menu, you would have the following “Stack view” customization options. Group Stacktraces From This option controls how frames are concatenated in step 1 of the algorithm above and what would be root nodes in the tree. Last Frame — aka the “hot methods” mode. Thread Root — aka the “call tree” mode. Distinguish Frames By This option controls which information is stripped from the frame description in step 1. Take note of the “Line Number” option. Line Numbers add clutter to the tree, but sometimes you want them to be visible to get the exact reference at source code. Layout Options The default tree is horizontally compressed, which is good but could confuse fresh eyes. Here you can switch back to a classic tree presentation. Stack view would work for all reports based on events that incorporate stack trace (including method profiling). Mission Control could even mix different types of events in the same stack view, but be aware that mixing events sampled by different rules (e.g., method profiling and IO traces) does not make much sense. Why CPU usage is important I mention what CPU metrics are important during profiling with JDK Flight Recorder. If we look at Java thread at any given time, it could be running on CPU in Java code ready to run in Java code but waiting in OS queue to get CPU running on CPU in native (non-Java) code ready to run in native code but waiting in OS queue to get CPU waiting to do something in native non-Java code waiting/blocked at the Java level JFR samples only threads in categories 1 or 2. There are also separate sampling for 3, 4 and 5, which is not well exposed in any dedicated report in Mission Control at the time of writing. So, if your server is starving on CPU, JFR will show you a particular picture, but it would be skewed due to mixing 1 and 2 categories. Sampling picture and would not accurately reflect real code computation cost in this situation. If the server is not CPU starved and threads you are profiling are actively consuming CPU, method sampling is the right tool for you to start with. If the server is not CPU starved and threads you are profiling are low on CPU, you are likely to have a “cold” hotspot, falling into categories 5 and 6. Category 5 code you can analyze with the “Native method sampling” event. Category 6 is either contention or IO, examined with separate reports. So by looking at CPU, you get an idea which report would be most useful next. Retrospective thread CPU usage in Mission Control What if you have a JFR recording and have no idea of CPU usage by threads at runtime? No problem, that data is recorded in a JFR file as well. You need to go to the “Event Browser” and find the “Thread CPU Load” event under the “Operating System” / “Processor” category. Here CPU usage for each thread is recorded with 10-second intervals. Remember that the percentage is taken from the total number of cores: in my screenshot, 25% would mean 100% of a single CPU core. JVM and host OS CPU usages are also recorded. Look under the “CPU Load” event type in the same category. Limit scope to a subset of threads Cool, now you can find CPU hungry threads, but the “Method Profiling” report shows all threads. Filtering by a subset of threads would be a good idea. Mission Control has very flexible and very nonobvious filtering features. We have to have only a subset of threads in the “Method Profiling” report. Here are the steps. Go to the “Threads” view Select one or more threads in a list of threads on the left Right click and choose “Store Selection” in the context menu Go back to “Method Profiling” In the filter combo select source “Threads Histogram Selection” and aspect “Thread Name is …” Now report includes events only for threads you have chosen. Any filter is shared across all reports, so do not forget to reset it then switching reports. What do I see in the “Stack Trace” view of the “Threads” report? While walking through the steps above, you may have noticed that the “Stack Trace” view is available in “Threads”. Moreover, it was reacting to selection in the thread list. How is the “Stack Trace” on the “Threads” report different from the “Method Profiling” report? Each report has a scope: a set of events (usually certain types of events) used to calculate the report. When you are working with report UI, a focused subset of events in scope is maintained behind the scene. A filter could narrow the scope, and this is what we did in the previous section. Any event may include the stack trace, and many do. The “Stack Trace” view listens to a focused set of events in an active report and visualizes all stack traces available. In “Method Profiling,” only method profiling samples are in scope. The “Threads” report includes almost every event bound to some thread (both sampled and non-sampled). So, while technically they could be aggregated together, it does make little sense. Nevertheless, you can use filters to fix this to make the “Threads” report show the same picture as “Method Profiling”. Go to “Event Browser” Find “Method Profiling Sample” is event types tree Right click on it and choose “Store Selection” in the context menu Go back to the “Threads” report In filter combo select source “Event Types Tree Selection” and aspect “Event Type is Method Profiling Sample” Now your “Stack Trace” would include only method profiling data, and you can change select threads without switching reports. Caveats of JFR method profiling The way JFR takes stack traces during profiling is precise. The majority of Java profilers use a JVM wide thread dump to get traces of all threads at once. A thread dump is a Stop the World operation, and doing it 100 times per second could be expensive. JFR manages to avoid Stop the World. Instead of stopping all threads at once, JFR stops them individually (using OS facilities) and captures stack traces from individual threads. Entering/leaving Stop the World state involves sophisticated protocol between application and system threads in JVM, and JFR skips this overhead altogether. Yet, this optimization introduces certain nuances data captured by JFR. Only stack traces ending up in Java code (not the native code) are recorded and visible in the “Method profiling” report. Thus, unless threads you are looking at were active CPU consumers, the picture in “Method profiling” would be skewed. Besides “Method Profiling Sample”, there is the “Method Profiling Sample Native” event type which is capturing threads in states 4, 5 and 6 (see the list mentioned earlier). However, this event type is available only in the “Event Browser” not in the “Method Profiling” or the “Threads” reports. The good news is that the “Stack Trace” view also works in the “Event Browser” report. Another important caveat for all types of Java sampling profilers is the infamous “safepoint bias.” In short, due to various effects of JVM runtime and JIT compiler, profilers could incorrectly identify the exact hot method or code line. In some edge cases, profilers could be hilariously inaccurate. Technically, JFR avoids the “safepoint” part of the problem, though it is still biased due to the way Java JIT compiler works. Here you can read more about a few examples of such hilarious profiler inaccuracy. While the edge case of the “safepoint bias” effect could make you lose your trust in the profiler, it is rarely a problem in actuality. A profiler’s job is to narrow the scope of search; it doesn’t need to be 100% accurate to be useful. But do not trust it blindly either. “Cold” code hotspots A hotspot is a portion of code (e.g., method or line of the source file) responsible for a considerable part of execution time during request processing (or other kinds of workload) related to other code involved. But how was that time spent? Was that code burning CPU cycles or just sitting cold off the CPU waiting for something? “Cold” hotspot is a kind of code that consumes time, but does not consume CPU resources. Most typical kinds of “cold” hotspots are Blocking IO calls Contention points in multi-threaded applications Waiting for completions of async tasks The method sampling approach used by JFR is not suitable for identifying “cold” host spots (it could see only the “CPU hot” code). There are separate reports for blocking IO and contention. But how would you know what you are dealing with a “cold” hot spot? Low thread CPU usage for a thread supposed to process requests is a symptom that should trigger your attention. File I/O report This report is composed of “File Read” and “File Write” events, capturing blocking file IO operations. Since events include file name, this report shows at the file level how many bytes were read/written to each file and how much time it has taken. An important caveat here is to consider the JFR recording session’s threshold; only operations exceeding it would be visible here. The report is relatively simple but could be quite powerful if combined with the filtering capabilities of Mission Control. Naturally, filtering and “Stack Trace” views are also available here. Socket I/O report The Socket I/O report is fairly similar to File I/O, but aggregation is shown by remote address and port. The taskbar lists three aggregation options available. By Host By Port By Host and Port Lock instances report Lock instances reports are based on the “Java Monitor Blocked” event type. These events are produced when a Java thread is blocking trying to acquire the semaphore. This report is only useful with “old style” synchronized keyword-based thread coordination (and by old I do not mean outdated, “synchronized” has its case in modern Java). What about “java.util.concurrent” based contention? JFR equally has events related to “new style” synchronization “Java Thread Park”, though there have been no dedicated reports in Mission Control so far. Threads report We’ve already had a brief interaction with the “Threads” report. It consists of a thread list and a timeline area. The thread list is simply a list of threads, but the timeline is more interesting. It has lanes for each thread and could visualize a wide range of JFR events. By default, they are so-called “Java Latency” events, but you can customize that via the context menu (see “Edit Thread Activity Lanes” option). Typically, individual events on the timeline would be tiny. You can hover to get one’s details under the mouse pointer, but it is not very useful if events are subpixel sized. You can zoom, though, drag the rectangle over the timeline and hit “Zoom to Selected Range” from the context menu to zoom in. You can use the time range selected in the “Threads” report as a filter on other reports, too. Conclusions In this article, I have shown key features of JDK Flight Recorder / Mission Control most useful to code execution profiling or hunting “code hotspots.” From its length, you could assume that Mission Control is not the most intuitive tool. This is true, and one should keep in mind a few fundamental principles to use it efficiently. Flight Recorder could be started for the command line, which is very helpful in environments where JMX access is impossible or complicated. In addition, it does not require any upfront JVM configuration; if your JDK supports JFR, you can start using it anytime. CPU usage is a crucial metric. It is essential you know which kind of hotspot you are looking for. If you cannot monitor CPU usage in real time, you can find this information in the Flight Recorder file. CPU consuming (“hot”) hotspots are very different from “cold” ones caused by code spending time in idle state. For a former traditional stack, trace sampling (the “Method Profiling” report) works fairly well. Later instances are more sophisticated. You may need to enable zero thresholds for I/O and contention events to fully picture idle state events in Mission Control. “Stack Trace” view is a potent tool you really need to get familiar with. It works for a wide range of events. For sampled events, you are most likely to be using “hot methods” mode, whereas “call tree” is more informative for traced ones. Performance is a hard topic. No matter how great your tools are, you need to learn JVM and OS’s inner workings. Yet tools are valuable and would save you lots of time. JDK Flight Recorder and Mission Control are a great combo, and them being completely open source is an additional advantage in my eyes. While the learning curve is steep, getting to know even a few tricks from this tool arsenal could help you greatly when dealing with performance issues in Java. Related Posts JDK Flight Recorder – a gem hidden in OpenJDK Hunting down memory issues with JDK Flight Recorder JVM in Linux containers, surviving the isolation JDK Flight Recorder, The Programmatic Way
- [7 reasons to switch from Oracle JDK to OpenJDK](https://bell-sw.com/announcements/2020/08/14/7-reasons-to-switch-from-Oracle-JDK-to-Liberica-JDK/): It seems that Oracle developed a habit of changing licensing conditions for Oracle JDK every two years: In January 2019, it stopped releasing free builds of Oracle Java SE 8 for commercial use; In 2021, the company announced a change in its licensing policy with the release of JDK 17. LTS-releases are now released every two years and according to a new license, receive free updates for 3 years. After that, enterprises have to migrate to the next LTS version or pay for commercial support; In 2023, the vendor overturned the pricing model completely by introducing the new “Employee for Java SE Universal Subscription” metric. According to it, the number of licenses is calculated based on the number of employees, including full-time, part-time, temporary employees and employees of agents, contractors, outsourcers, and consultants that support internal business operations. If you have stumbled upon this article, chances are that your enterprise considers migrating from Oracle JDK. In the upcoming paragraphs, we will compare Oracle JDK and OpenJDK so that you can decide whether migration will be beneficial for your project. You can also read more about the OpenJDK project in our What is OpenJDK article. Table of Contents What is Oracle Java? Are OpenJDK and Oracle JDK the same? What is the difference between Oracle JDK and OpenJDK? Is it difficult to migrate from Oracle to OpenJDK? Is OpenJDK secure? Is OpenJDK performant? Can you use OpenJDK for enterprise development? Can you use OpenJDK for closed-source applications? Does OpenJDK provide all the features Oracle JDK has? Comparison of OpenJDK and Oracle JDK Liberica JDK — OpenJDK distribution for modern Java for modern Java deployments 1. Liberica JDK provides the most complete Java experience 2. Liberica JDK is affordable 3. Liberica JDK is safe 4. Liberica JDK offers JDK Flight Recorder + Mission Control for free 5. Liberica JDK is easy to install 6. Liberica JDK is compliant with standards 7. Liberica JDK is in touch with the community Customer success stories VMware Tanzu OOCL JetBrains Flow Traders Have we convinced you to switch from Oracle JDK? What is Oracle Java? Oracle Java (or Oracle JDK) is a set of computer software and specifications initially developed at Sun Microsystems and then acquired by Oracle. Oracle Java rapidly gained popularity in various sectors, such as banking and fintech, e-commerce, game development, etc. In 2006, the OpenJDK project was born following the promise of Sun Microsystems to make Java code open-source. When Oracle stopped releasing free builds of Oracle Java SE 8 for commercial use and changed Java licensing policy, many enterprises turned to OpenJDK as a reliable and free alternative to Oracle Java. Are OpenJDK and Oracle JDK the same? Both Oracle JDK and OpenJDK are developed on the same code base, so the technological foundation is the same. But for many people, Oracle means a “prominent trustworthy company,” which is true, while OpenJDK means “some community trying their best making open source Java runtime” — and here is where they are wrong. Take BellSoft, one of the major OpenJDK enterprise contributors: before launching the company, its founders and engineers have worked at Oracle for years. They know Java™ on multiple levels like they know their own mind and do everything to meet the standards. Liberica binaries have passed all the Technology Compatibility Kit (TCK) tests for Java provided by Oracle and are fully ratified in the Java Community Process. What is the difference between Oracle JDK and OpenJDK? The pivotal difference between Oracle JDK and OpenJDK is licensing. OpenJDK is an open-source project licensed under GPLv2 + Classpath exception. On the other hand, Oracle Java is closed source, and enterprises need to purchase commercial licenses to receive Java updates. Oracle JDK versions are available under various licenses: Starting with Oracle JDK 17, the builds are available under the Oracle No-Fee Terms and Conditions license; Oracle JDK 11 and 8 are available via My Oracle support or under the OTN license that permits personal use. You can read more on terms and conditions of mentioned licenses in our article dedicated to Java licensing. Other differences are associated with additional functionality and supported platforms. For instance, Oracle Java doesn’t include Shenandoah GC, but it is shipped with Liberica JDK and other major OpenJDK distributions. A comprehensive comparison of features offered by the most popular OpenJDK distributions and Oracle Java can be found in the Oracle Java alternatives article. Is it difficult to migrate from Oracle to OpenJDK? No, it isn’t. Both are implementations of the same Java specification, so switching requires little to no adjustments. Your engineering team could pick a Liberica JDK container image right now, literally change one line of code, and use the base distribution without issues. Is OpenJDK secure? The latest OpenJDK releases are immensely safer than outdated releases from Oracle with a permissive license. The OpenJDK community members, both companies and individual developers, constantly hunt down vulnerabilities and bugs and promptly fix them. For instance, BellSoft signed the OCTLA and became a part of the Vulnerability Group alongside other major distributors such as Oracle, SAP, Amazon, and Red Hat. Together they search for, review, and patch security issues. Is OpenJDK performant? From the JVM performance point, it has no differences from Oracle JDK. Moreover, the performance equality is confirmed by major industry-standard benchmarks (SPECjbb, SPECjvm) and microbenchmarks embedded in the OpenJDK code. Can you use OpenJDK for enterprise development? OpenJDK is an optimal choice for enterprise development. As we stated earlier, it is performant, reliable, and secure. With OpenJDK, you are able to use the runtime without any vendor participation. At the same time, it is possible to acquire commercial support from an OpenJDK vendor at much more affordable prices than from Oracle. If you choose to work with a vendor, it is good to have a clear idea of which company is the supplier of your underlying technology to avoid vendor lock-in. BellSoft guarantees no lock-in by providing builds of vanilla OpenJDK and contributing all the fixes upstream. Can you use OpenJDK for closed-source applications? Copyleft licenses, such as the GNU General Public License (GPL), are considered viral. Any code that uses a GPL library automatically becomes GPL as well. Contrary to popular belief, OpenJDK does not mean that anything linking to it needs to be distributed under the GPL terms. The license for OpenJDK is not just “GPL v2”, it is “GPL v2 with the Classpath Exception.” It allows linking OpenJDK with any independent module regardless of GPL terms, copying and distributing the resulting files under terms of your choice. Obviously, this independent module can be your proprietary code, which means that you may utilize the GPL components of OpenJDK while maintaining the integrity of your intellectual property. Your products thus may be copyrighted under a different license. Does OpenJDK provide all the features Oracle JDK has? If we talk about most versions after Java 8, the current OpenJDK and Oracle JDK releases show no differences. However, version 8 has certain technologies absent in OpenJDK (that are gradually being backported). The table below shows all the qualities and features that distinguish Liberica JDK from Oracle JDK with some alternative tools that may replace the original Oracle JDK 8 components. Comparison of OpenJDK and Oracle JDK The table below demonstrates the difference between OpenJDK and Oracle Java using Liberica JDK as an example. Liberica JDK Oracle JDK License, Security, Compatibility, and Sources Based on OpenJDK Yes Yes Open-source license, no field of use restrictions Yes Versions up to JDK 17 are under OTN. Starting with JDK 17, the license is NFTC Quarterly Security Updates Yes Yes Compliance with Java SE specification guaranteed by TCK Yes Yes Performance Parity with Oracle Java SE Yes Yes Guaranteed LTS (8, 11) and Feature (12, 13, 14) versions Yes Yes Platforms & Installers Windows x86 (64 bit) Yes Yes Windows x86 (32 bit) Yes JDK 8 only macOS Yes Yes Linux x86 (64 bit) Yes Yes Linux x86 (32 bit) Yes JDK 8 only Alpine Linux x86 (64 bit, musl) Yes Yes Linux ARM (32 bit) Yes JDK 8 only, Embedded license Linux ARM (64 bit) Yes JDK 8 only Solaris Sparc JDK 8, 11 JDK 8, 11 Solaris x86 (64 bit) JDK 8, 11 JDK 8 only Windows Installers MSI EXE Mac Installers DMG, PKG DMG Linux Installers DEB, RPM, TAR.GZ DEB, RPM, TAR.GZ Variety of bundle types Lite, Standard, Full, JDK/JRE Standard, Server JRE/JDK Linux Repositories (yum, apt) Yes No Product Features JDK Flight Recorder JDK 8+ JDK 8, JDK 11 Java Mission Control Yes Yes OpenJFX (JavaFX) JDK 8, 11, 17 JDK 8 only Java Web Start and Applets JWS is supported in the Commercial Bundle with OpenWebStart from Karakun Yes Graphics Renderer Marlin in JDK 8, 11 Ductus in JDK 8, Marlin in JDK 11 Font rendering engine Freetype in JDK 8, 11 T2K in JDK 8, Freetype in JDK 11 Commercial Support Provided for Supported Platforms & Environments Yes Yes 24x7x365 Support (1-hour SLA) Yes Yes SLA for Quarterly Updates Yes Yes Out-of-cycle Critical Fixes (independent from OpenJDK) Yes Yes Commercial Support Production Lifecycle 2031 for Java SE 2026 for JavaFX 2030 for Java SE 2025 for JavaFX Engineering capacity to root-cause and fix bugs (independent from OpenJDK) Yes Yes As you can see, migration to OpenJDK doesn’t pose any security or compatibility risks. On the contrary, it will enable you to eliminate vendor lock-in and optimize software expenses. The next big question is: which OpenJDK distribution to choose? Liberica JDK — OpenJDK distribution for modern Java for modern Java deployments 1. Liberica JDK provides the most complete Java experience Migration to Liberica JDK enables you to cover all your Java needs: The Liberica JDK binaries are supported on most existing platforms and system configurations; BellSoft supports legacy Java versions 6 & 7, all LTS versions (8, 11, 17), feature releases, and GraalVM; We developed a special Liberica JDK Lite version optimized for cloud deployments, which helps to minimize resource consumption; You can utilize Liberica JDK for Embedded for systems with lower performance. In addition, to Liberica JDK you gain access to other useful Java utilities: Convert Java apps into native images with accelerated startup using our GraalVM-based Liberica Native Image Kit. It is utilized by default with Cloud Native Buildpacks and recommended by Spring as a Native Build tool; Control and update all corporate Java runtimes from one window with Liberica Administration Center; But there is more! In 2022, our engineers developed Alpaquita Linux, a lightweight distro that comes with two libc implementations (optimized musl and glibc) and Java tools facilitating the development and deployment of Java applications. Coupled with Alpaquita, Liberica JDK enables you to build the smallest containers on the market that can take up as little as 38 MB! And if you seek higher availability and predictable scalability in the cloud, Liberica JDK supports Coordinated Restore at Checkpoint API that gives your Java apps almost instant startup at peak performance and allows for optimized resource consumption. Follow our guide on using Java with CRaC in a container and give this feature a try. As a result, by switching from Oracle to Liberica JDK, you save on support, but at the same time create a full software stack tailored to your needs. 2. Liberica JDK is affordable You may use our binaries for free. But if you would like to have a reliable partner who is there for you 24/7, and gives feedback on your query within 24 hours based on SLA, BellSoft offers versatile support plans with per-instance pay which are much less costly than Oracle’s, considering that Oracle counts employees, not equipment. For example, you have 50 servers and 250 employees. You will pay $15,000 per year for Liberica JDK or $45,000 per year for Oracle Java. In case you have 500 servers and 2,500 employees, the annual subscription for Liberica JDK will be $70,000 and for Oracle Java — $360,000. The calculation is based on the prices effective in November 2022 and presented on corresponding Oracle Java SE Universal Subscription Global Price List and Liberica JDK support price pages. 3. Liberica JDK is safe We provide continuous updates for the current and LTS versions with bug fixes and security patches as part of the CPU (critical patch update) quarterly release cycle. Commercial customers also receive off-cycle and extra patches not yet included into the OpenJDK code. 4. Liberica JDK offers JDK Flight Recorder + Mission Control for free These indispensable tools were first introduced in Oracle Java 7, along with other features that required licensing and subscription. Since the release of JDK 8u262, everyone can use them in all Liberica versions unconditionally. 5. Liberica JDK is easy to install We have more channels to deliver Liberica JDK to our customers than any other vendor. Starting with Java 17, Oracle JDK is released under a new “Oracle No-Fee Terms and Conditions” (NFTC) License, where sanction restrictions are explicitly mentioned. New Oracle JDK builds are provided on the official website of the company. Previous Oracle JDK versions are still licensed under Oracle Technical Network (OTN), and OTN serves as a centralized repository for registered Oracle users. BellSoft offers a broad choice: Pick a container image on Docker Hub; Download a Windows auto-installer; Install a repository for Linux; Find builds on package managers; Download binaries directly from the website or through our public API. Not to mention early access builds that we provide for all update releases so that our clients could test them beforehand. Note that the build with the JFR port for JDK 8 is included into release builds of Liberica JDK and builds of other OpenJDK vendors with HotSpot. However, Oracle has a closed JFR implementation, this function is provided with a flag on a commercial basis. In addition, if you use buildpacks to automate the containerization process, Liberica JDK is the default JVM in Paketo buildpacks. 6. Liberica JDK is compliant with standards We believe in quality level testing for our customers’ peace of mind. All produced binaries are verified by TCK for Java SE specifications. Moreover, Liberica JDK for macOS is a notarized product, which means you can refer to it when developing your applications on this platform. 7. Liberica JDK is in touch with the community Being among the major OpenJDK contributors, we make sure that any bug we fix for a client during troubleshooting is immediately corrected for everyone. We do not have a downstream repository, so our fixes get integrated into the JDK mainline branch as fast as possible, unlike some other vendors that work towards eventual consistency with OpenJDK. To learn more about Liberica JDK technical characteristics and use cases, download the Product Summary by clicking the button below. Download Liberica JDK white paper Customer success stories Millions of users all over the world already use Liberica JDK. The majority of our clients have traveled the same path you are going to and migrated from Oracle JDK to Liberica. Let’s see what they say about their digital transformation and overall experience with BellSoft. VMware Tanzu VMware is a leading provider of multi-cloud services for all applications. Over the years, BellSoft has supported VMware’s JDK development efforts by providing Liberica JDK. Satisfied with timely and reliable support, VMware invited BellSoft to extend the relationship by providing compiler and Java Runtime support for Spring Native — a recent addition to the Spring Framework ecosystem for compiling Spring Boot applications into native executables. VMware now provides their users with both native support for Spring and Liberica Native Image Kit through the Native Image Buildpack. OOCL OOCL is one of the world's largest integrated international container transportation and logistics companies. OOCL provides transportation services in Asia, Europe, the Americas, Africa and Australasia. Liberica JDK delivers the stable and secure Java environment that helps OOCL to provide its customers with fully-integrated logistics. JetBrains We asked Konstantin Bulenkov, JetBrains Runtime Lead: Why did you choose BellSoft as the partner to support JetBrains Runtime? “When Apple made the decision to discontinue support for JDK on macOS, we ended up facing a major problem with respect to text rendering quality in the IntelliJ editor. So we decided to start the JetBrains Runtime project. The biggest fear we had before making the switch to our own runtime was security. And Oracle’s move last year to stop providing public updates for JDK 8 forced us to work on finding a way to make JetBrains Runtime more secure. We realized that Liberica JDK is a 100% open source project, and BellSoft is one of the leading OpenJDK contributors. BellSoft, part of OpenJDK Vulnerability Group, collaborates with other contributors to keep OpenJDK robust. JetBrains relies on the Liberica team’s experience and expertise to provide timely updates for our customers; together, we keep JetBrains Runtime secure and performant.” Have we convinced you to switch from Oracle JDK? Leave your details by clicking the button below, consult with BellSoft’s professional team and start your migration now! Flow Traders We asked Yury Vasyutinskiy, Team Lead at Flow Traders: What were the essential aspects you sought in a Java runtime vendor? “Flow Traders is a leading global financial technology-enabled liquidity provider in financial products, historically specialized in Exchange Traded Products (ETPs), now expanding into other asset classes. Flow Traders ensures the provision of liquidity to support the uninterrupted functioning of financial markets. It allows investors to continue to buy or sell ETPs or other financial instruments under all market circumstances. We continuously grow our organization, ensuring that our trading desks in Europe, the Americas and Asia can provide liquidity across all major exchanges, globally, 24 hours a day. Technology is a key component in enabling this growth; therefore, we aim to be in as much control as possible to our trading platform. Thus, we develop and maintain a large code base with dedicated in-house resources. Due to the change in the licensing model for Oracle JDK, we have decided to look around for other JDK vendors. We embrace innovation and new technologies, as we believe that helps us to step up our game. But we stay pragmatic and check all the details thoroughly. Hence several requirements were identified for switching to a new vendor solution: Stability of the distribution; Clear SLA on security patches delivery; Full compatibility with our landscape; Performance of the distribution. At Flow Traders, we invest significantly in the quality of our products. Being one of the leading global ETP liquidity providers, we have extensive quality control systems that cover both functional and non-functional aspects of our products. We have conducted a series of different tests to check all the requirements. As a result, Liberica JDK was chosen as our main JDK distribution. We are satisfied with the Liberica JDK as we managed to onboard it without any major hiccups. We are also very happy with the level of support provided by the Bellsoft team and are looking forward to strengthening our relationship in the coming years.” Have we convinced you to switch from Oracle JDK? Leave your details by clicking the button below, consult with BellSoft’s professional team and start your migration now! Contact us!
- [Hunting down memory issues
with JDK Flight Recorder](https://bell-sw.com/announcements/2020/09/02/Hunting-down-memory-issues-with-JDK-Flight-Recorder/): In this post about JDK Flight Recorder and Mission Control, a powerful diagnostic and profiling tool built into OpenJDK, I would like to focus on JVM memory features. What are the memory problems? If you put the words “garbage collection” and “problem” into one sentence, many would think of the infamous Stop-the-World pauses that JVM is prone to trigger. Lengthy Stop-the-World pauses are indeed very problematic from an application perspective. While garbage collection (GC) and Stop-the-World (STW) pauses are connected, they are not the same. The problem with GC is likely to manifest itself as prolonged STW pauses, but GC does not necessarily cause them. General optimization of application code allocation patterns is another standard task, and Flight Recorder with Mission Control could be of great help here. Optimized memory allocation in application code suits both for GC and overall execution performance. Finally, the application code may have memory leaks. Though I believe heap dumps are the best tool for diagnosing memory leaks, JDK Flight Recorder offers an alternative approach with its pros and cons. In particular, heap dumps require Stop-the-World heap inspection to capture and tend to be extensive (depending on the heap size), while JFR files do not grow proportionally to the heap size. Below is a plan for the rest of this article. Garbage collection related reports JVM Operations aka the Stop-the-World pause report Memory allocation sampling Old object sampling overview Starting memory profiling JDK Flight Recorder session You would need OpenJDK with JDK Flight Recorder support and Mission Control. I was using Liberica OpenJDK 11.0.7+10 and Liberica Mission Control 7.1.1. There are multiple ways to start a JDK Flight Recorder session. You can do it with Mission Control UI (locally or via JMX), with jcmd from console, or even configure it to auto-start with JVM command-line arguments. All these options are covered in my previous post, so I will omit the details here and jump right to Flight Recorder session settings. The configuration dialog of the Flight Recorder session in Mission Control has a few options relevant to the features I am going to explain later. Garbage Collector The default level is “Normal,” and it should be enough. You may choose “All,” which adds more details about GC Phases. Memory Profiling Here you may choose one of three options: Off Object Allocation and Promotion All, including Heap Statistics I strongly suggest you stay with “Object Allocation and Promotion.” It provides valuable data points for allocation profiling with low overhead. Heap Statistics included in the last option force regular Stop-the-World heap inspections which could be quite lengthy. Memory Leak Detection If you suspect memory leaks in your application, tweak these options. You need to choose the “Object Types + Allocation Stack Traces + Path to GC Root” level of details to see the leaking path in the report afterward. Calculating “Path to GC Root” is a moderately expensive Stop-the-World operation, and I would not recommend enabling it just as a precaution. Garbage collection in OpenJDK OpenJDK JVM can use different implementations of garbage collectors. The most commonly used ones in HotSpot JVM are Garbage First Parallel Mark Sweep Compact Concurrent Mark Sweep (CMS) — discontinued in Java 14 There are several other algorithms available in various JDKs, but I am not going to get too deep for the sake of brevity. Each GC has its unique features, but a few are the same. All the algorithms above are generational, meaning they split available heap space as separate young and old space. The result is three types of GC cycle: Minor (young) collections — recycle only young space Major (old) collections — recycle old and young space Last resort full collections — a big STW whole heap collection you want to avoid Each GC cycle is split into a hierarchy of phases. Some could be concurrent (executing in parallel with application code), but most require a Stop-the-World pause. Garbage collector report Mission Control has a generic “Garbage Collections” report agnostic of the GC algorithm used by JVM. This report shows a list of GC pauses with break down by phases (table on the right). Information in this report could replace the typical usage of GC logs. Here you can quickly spot the longest GC pause and dig a little deeper into its details. Notice GC ID assigned to each GC cycle: they are useful to pick additional events related to GC but not included in this report. Two key metrics could be used to assess GC performance regardless of the algorithm: Max duration of a Stop-the-World pause — it may add up to your application’s request processing time, hence increasing response time. Percentage of time spent in a Stop-the-World pause during a certain period — e.g., if we spent 6 seconds per minute in GC, the application can utilize 90% of available CPU resources at most. The chart on the “Garbage collection” report helps to spot both metrics easily. “Longest pause” and “Sum of pauses” visualize these metrics aggregated over regular time buckets (2 minutes in the screenshot). “Longest pause” shows the longest GC pause in the bucket, so you can observe your regular long pauses and outliers, if applicable. “Sum of pauses” shows the sum of all GC pauses in the bucket. Use this metric to derive the percentage of time spent on GC pauses. Hover your mouse over the bar, and you will see the exact numbers in the tooltip. In the example screenshot, the sum of pauses over 2 minutes equals 4 seconds. GC overhead is 4 / 120 ≈ 3.3%, which is reasonable. What if you are not happy with your GC performance? GC tuning is a big topic out of scope for this article, but I will highlight a few typical cases. ‘Heap is too small’ problem GC in modern JVM usually just works. A lot of engineering effort has gone into making GC adaptable for the application needs. Although GC is not magic, it needs enough memory to accommodate application live data sets and some head room to work efficiently. If the heap size is not enough, you will see increased GC overhead (percentage of time spent on GC pauses) and, as a consequence, the application slowing down. The screenshot above illustrates JVM running on low memory. During the hovered time bucket, 1.4 seconds out of 2, JVM was in a GC pause, which is an absolutely unhealthy proportion. Often increasing the max heap allowed to JVM solves the problem with high GC overhead. However, if an application has a memory leak, increasing heap size is no help. The leak in such a situation needs to be fixed. ‘Metaspace is too small’ problem Metaspace is a memory area to store class metadata. Starting from Java SE 8, metaspace has not been part of heap, even though the lack of available space in metaspace may cause GC there. To free some memory in metaspace, JVM needs to unload unused classes. Class unloading, in turn, requires a major GC on the heap. The reason is that objects on the heap keep references to corresponding classes, and a class cannot be unloaded until any instance of it exists in the heap. You would quickly spot the metaspace keyword in the “Cause” column in this case. You can also view the metaspace size in the timeline chart at the bottom of the report to check if you are hitting the limit. Special reference abuse Java™ offers utility classes that have very specific semantics related to garbage collection. These are weak, soft and phantom references. Besides, JVM is using finalizer references internally to implement the semantics of finalizers. All these references require particular processing within the bounds of a GC pause. Due to semantic special references, they can only be processed after all live objects are found. Usually, this processing runs fast, but abusing these special references (especially finalizers as they are most expensive) could cause abnormally long pauses for otherwise healthy GC. Columns with a count of each special reference type are on the right side in the GC event table. Numbers on the order of tens of thousands could indicate a problem. You can also find the reference processing phase in the “Pause Phases” table and see how much time was spent on it. More GC related events The “Garbage Collections” report is a handy tool to spot standard problems, but it does not display all GC details available from JDK Flight Recorder. As usual, you can get to the event browser and look at specific events directly. Look under Java Virtual Machine / GC / Details for more diagnostic data related to garbage collection. Besides work done under Stop-the-World, some collectors (G1 in particular) use background threads working in parallel with application threads to assist in memory reclamation. You may find details for such tasks if you look at the “GC Phase Concurrent” event type. Stop-the-World pauses in OpenJDK A Stop-the-World (STW) pause is a state of JVM when all application threads are frozen, and internal JVM routines have exclusive access to the process memory. Hotspot JVM has a protocol called safepoint to ensure proper Stop-the-World pauses. STW pauses are mainly associated with GC activity, but it is but one possible reason. In Hotspot JVM, STW pauses get involved in other special JVM operations. Namely: JIT compiler related operations (e.g. deoptimization or OSR) Bias lock revocation Thread dumps and other diagnostic operations, including JFR-specific ones As a rule, STW pauses are unnoticeably short (less than a millisecond), but putting JVM on a safepoint is a sophisticated process. Things can go wrong here. In the previous post, I mentioned the safepoint bias. Safepoint protocol requires each thread executing Java code to explicitly put itself in a “safe” state, where JVM knows exactly how local variables are stored in the memory on the stack. After it starts, each thread running Java code receives a signal to put itself on the safepoint. A JVM operation cannot begin until all threads have confirmed their safe state (threads executing native code via JNI are an exception; STW does not stop them unless they try to access heap to switch from native code to Java). If one or more threads are slow at reaching the safe state, the rest of the JVM will wait for them to be frozen effectively. There are two main reasons for such misbehavior: A thread is blocked while accessing a memory page (d state in Linux) and cannot react A thread is stuck in a hot loop, where the JIT compiler omitted a safepoint check as it was considered too fast of a loop. The first situation may happen if the system is swapping or application code is using memory-mapped IO. The second one may occur in heavily optimized computation-intensive code (in Java runtime, java.lang.Sting and java.util.BigDecimal operations over large objects are common culprits). VM Operations report Mission Control has a report with a summary of all Stop-The-World VM operations. This report shows a summary grouped by type of operations. In the screenshot, “CGC_Operation” and “G1CollectForAllocation” are the only operations related to GC (G1 in particular). You can see a fair number of other VM operations, but they are very quick. Also, bear in mind that many operations are caused by JFR itself, such as “JFRCheckpoint,” “JFROldObject,” “FindDeadlocks,” etc. Problems with safepoints are relatively rare in practice, but spending a few seconds to quickly check the “VM Operations” report for abnormal pauses is a good habit. Memory allocation profiling While JVM is heavily optimized for allocating a lot of short-lived objects, excessive memory allocation may cause various problems. Allocation profiling helps to identify which code is responsible for the most intense allocation of new objects. Mission control has a “Memory” report giving an overview of object allocation in application code. This report shows a timeline of the application’s heap allocation rate and a histogram of allocation size by object type. At first glance, the report looks too ascetic and done in broad strokes, notably if you have used Mission Control 5.5. There are many more details to squeeze out from this report with Mission Control magic, but we need to get an idea of the source for these numbers. How are allocation profiling data collected? Allocation profiling in JVM was a rather challenging task. While previously many profilers had allocation profiling features, the performance impact on an application with allocation profiling turned on was inconvenient and often prohibitive. Allocation profiling is based on sampling. A runtime allocation profiler collects a sample of allocation events to get the whole picture. Before JEP-331 (available since OpenJDK 11), profilers had to instrument all Java code and inject extra logic at every allocation site. The performance impact from such code mangling was dramatic. JDK Flight Recorder, on the contrary, was always able to use low overhead allocation profiling. The key is to record TLAB allocation events instead of sampling normal ones. Almost all new Java objects are allocated in the so-called Eden (a part of young object heap space). Not to compete for shared memory management structures, each Java thread reserves a thread local allocation buffer (TLAB) in Eden and allocates new objects there. It is an essential performance optimization for multicore hardware. Eventually, the buffer dries up, and the thread has to allocate a new one. This type of event is the one recorded by Flight Recorder. The average TLAB size is roughly 500KiB (size is dynamic and changes per thread), so one allocation per ~500 KiB of allocated memory is recorded. This way, Flight Recorder does not add any overhead to the hot path of object allocation, yet it can get enough samples for further analysis. Each sample has a reference to the Java thread, stack trace, type and size of the object being allocated, as well as the size of the new TLAB. You can find raw events in the event browser under “Allocation in new TLAB.” But how would I know which code is allocating the most? The “Memory” report is based on the “Allocation in new TLAB” events, which has associated thread and stack trace. It means we can use the “Stack Trace” view and filtering to extract much more details from this report. Typical workflow for identifying memory hot spot would look as follows: Open the “Memory” report. Open the “Stack Trace” view (enable via Window > Show View > Stack Trace if hidden). Enable “Show as Tree” and “Group traces from last method” on the “Stack Trace” view. Such a configuration is the most convenient for this kind of sampling. Switch “Distinguish Frames By” to “Line Number” in the context menu of the “Stack Trace” view if you want to see line numbers. By this point, you can already see a top hot spot of allocation in your application. You may also select a specific object type in the “Memory” report to only examine these type-related allocation traces. Further, you can select a range of timelines to zoom in on a definite period. Another functionality here is applying filters to narrow the report to individual threads. All the tricks for the “Method Profiling” report described in the previous post would work here too. Live object sampling Memory leaks are another well-known problem for Java applications. Traditional approaches to memory leak diagnostics rely on heap dumps. While I consider heap dumps a vital tool for dealing with memory troubleshooting optimizations, they have their drawbacks. More specifically, heap dumps could be quite large, slow to process and require Stop-the-World heap inspection to capture. Flight Recorders offer an alternative approach, live object sampling. The idea is simple: we already have allocation sampling. Now from the newly allocated objects, Flight Recorder picks few to trace across their lifetime. If an object dies, it is excluded from the sample (and no event is produced). Then events are dumped into a file at the end of recording. Flight Recorder may optionally calculate and record the path to the nearest GC root for each object in a live object sample (a configuration option mentioned at the beginning of this article). This operation is expensive—requiring an STW heap inspection—but necessary to understand how an object is leaking. But these are just random objects. How would sampling help find a leak? Normal objects are short-lived, but leaked objects could survive for long. If sampling JVM for a long enough time, you will likely catch a few leaked objects in the sample. Once these objects get to the final report, we can identify where they are leaking via the path to the nearest GC root, also recorded. Mission Control has a “Live Objects” report to visualize this kind of event. You would also like the “Stack Trace” view to be enabled here. In the table, you can see a sampled object grouped by GC root. You can unfold a reference path from a GC root down to individual objects from the sample. What is great is that you can see the allocation stack trace of each sampled object in the “Stack Trace” view as well. This information is unique to Flight Recorder and cannot be reconstructed from a regular heap dump. Does it work? This feature is relatively new in Flight Recorder, whereas heap dump analysis is a reliable, time-tested technique. A live object sample in Flight Recorder is rather small, and it is a matter of luck whether it would catch a problematic object or not. On the other hand, Flight Recorder files several orders smaller in magnitude compared to heap dumps. While Old Object Sampling is definitely not a silver bullet, it offers a fascinating alternative to the traditional heap dump wrangling approach for memory leak investigations. Conclusions Memory in JVM is a complicated matter. JDK Flight Recorder and Mission Control have many features to help users with typical problems. Nonetheless, the learning curve remains steep. On the bright side, complex JVM machinery such as GC and safepoints usually works fine on its own, so there is rarely a need to dig deep. Still, it is wise to know where to look if you have to. Memory optimization is a regular task. The allocation profiling features in Flight Recorder and Mission Control are highly beneficial in practice. If you asked me about the single most crucial Flight Recorder component, I would name allocation profiling without a second thought. Live object sampling is also an appealing approach for memory leak detection. Unlike heap dumps, it is much more appropriate for automation and continuous usage in production environments. Related Posts JDK Flight Recorder – a gem hidden in OpenJDK Hunting down code hotspots with JDK Flight Recorder JVM in Linux containers, surviving the isolation JDK Flight Recorder, The Programmatic Way
- [Liberica JDK 15 brings efficiency and flexibility](https://bell-sw.com/announcements/2020/09/17/Liberica-JDK-15-brings-efficiency-and-flexibility/): It’s time to welcome the much-awaited version 15 of OpenJDK. The new release has 14 new features, 2949 bug fixes and backports in total, with eight issues resolved by the BellSoft team (like Thread-local handshakes implemented for Arm32). Let’s discuss the highlights introduced in JDK 15. 1. Production-Ready ZGC (JEP 377) and Shenandoah GC (JEP 379) Both these garbage collectors have been changed from experimental features to product features. They are low-latency GCs. ZGC is scalable, first integrated to JDK 11; Shenandoah GC is low-time-pause and appeared in JDK 12. The collectors attempt to tackle the common problem of too long stop-the-world pauses by using colored (Z) or Brooks (Shenandoah) object pointers for efficient concurrent work. They are enabled via -XX:+UseZGC and -XX:+UseShenandoahGC command-line options, respectively. ZGC and Shenandoah GC are supported on Linux x86_64, AArch64, Windows, and macOS. Shenandoah GC has been backported to JDK 11 and will become available in version 11.0.9 mid-October. G1 will remain the default garbage collector for all JDK releases starting from version 9. 2. Text Blocks (JEP 378) A text block is a multi-line string literal that prevents the need for most escape sequences, formats the string automatically and in an easy to predict manner. It also enables the developer to have control over the format, if they so desire. This functionality was first proposed in early 2019, targeted for JDK 12, but did not make it to the release. As a preview feature in versions 13 and 14, text blocks have changed their form a lot since. Starting from JDK 15, writing Java applications becomes ever more comfortable as these handy strings can be used to express several lines of source code and denote code written in other languages, increasing its readability. Text blocks are an excellent example of Java™ constantly evolving and a reminder for developers to use experimental features with caution, as they might end up far different from the first instance. Below are examples of text blocks in different languages. Scala: object ScalaTextBlock { def main(args: Array[String]): Unit = { val s = """def printHello(): Unit = { | println("Hello Text Block!") |} """.stripMargin println(s) } } /* Output: def printHello(): Unit = { println("Hello Text Block!") } */ Kotlin: fun main() { val source = """ fun printHello() = println("Hello Text Block!") """.trimIndent() println(source) } /* Output: fun printHello() = println("Hello Text Block!") */ Java: public class TextBlock { public static void main(String[] args) { var source = """ public void printHello() { System.out.println("Hello Text Block!"); } """; System.out.println(source); } } /* Output: public void printHello() { System.out.println("Hello Text Block!"); } */ 3. Edwards-Curve Digital Signature Algorithm (JEP 339) JDK 15 allows implementing cryptographic signatures based on the Edwards-Curve Digital Signature Algorithm (EdDSA). It is a modern elliptic curve signature scheme showing substantial benefits if compared to the other JDK signature schemes. For instance, at the same security level, EdDSA’s performance should improve over the Elliptic Curve Digital Signature Algorithm (ECDSA) based on native C code. It is the reason why EdDSA is rather popular and already supported in crypto libraries (OpenSSL, BoringSSL and many more). However, it will only be implemented in the SunEC provider, which would receive new Signature, KeyFactory, and KeyPairGenerator services. One of the upcoming JEPs will work on integrating EdDSA with JSSE for TLS 1.3. 4. Hidden Classes (JEP 371) Hidden classes cannot be used directly by the bytecode of other classes. They are meant to be used through reflection, indirectly, by frameworks that generate classes at runtime. Such a class may be defined as a member belonging to an access control nest (introduced by JEP 181) and unloaded independently of others. Enabling standard APIs to define not discoverable hidden classes with a limited lifecycle allows frameworks inside and outside the JDK to generate classes that could instead define hidden classes dynamically. Since many JVM languages, such as Clojure and Groovy, rely on dynamic class generation to be flexible, this feature will improve the efficiency of every programming language implementation built on the JVM. Hidden classes serve as a replacement for sun.misc.Unsafe::defineAnonymousClass, although they do not support its full functionality (such as constant-pool patching). The goal is to deprecate this non-standard API for complete removal in a future release. An example of a hidden class: import java.lang.invoke.MethodHandles; import static be.ClassLoad.getGeneratedClassBytes; import static java.lang.invoke.MethodHandles.Lookup.ClassOption.NESTMATE; import static java.lang.invoke.MethodHandles.lookup; public class HiddenClassExample { public static void main(String[] args) throws Exception { byte[] bytes = getGeneratedClassBytes(); MethodHandles.Lookup lookup = lookup() .defineHiddenClass(bytes, false, NESTMATE); Class> hiddenClass = lookup.lookupClass(); System.out.println("canonical name = " + hiddenClass.getCanonicalName()); System.out.println("name = " + hiddenClass.getName()); System.out.println("isHidden = " + hiddenClass.isHidden()); Object instance = hiddenClass.getConstructors()[0].newInstance(); System.out.println("toString = " + instance.toString()); } } /* Output: canonical name = null name = HiddenClass/0x0000000800bb1c40 isHidden = true toString = This is hidden class! */ 5. Remove the Solaris and SPARC Ports (JEP 381) Given that many OpenJDK projects (including the biggest ones, like Valhalla, Loom, and Panama) need considerable changes to CPU-architecture and OS-specific code, all source code related to the Solaris OS and SPARC architecture is removed from the latest release. The ports were already deprecated in JDK 14, and their removal in this release was not unexpected. Such a measure will help the OpenJDK community concentrate efforts, move HotSpot further, and enable the use of C++14 language features for HotSpot in JDK 16 next year. Liberica JDK’s LTS versions 8 and 11 will continue supporting both Solaris and SPARC—till 2031 and 2027, respectively. The change only concerns JDK 15 and further releases. 6. Disable and Deprecate Biased Locking (JEP 374) Biased locking is a legacy technique used in the HotSpot Virtual Machine to reduce the overhead of an uncontended lock and optimize synchronization. JDK 15 is the first release where biased locking is off by default (still enabled in LTS releases, 11 and 8). It can only be turned on if -XX:+UseBiasedLocking is set on the command line when HotSpot is started. The reason for this JEP is that the technique happens to be precariously costly to maintain while its performance gains are less evident than they were seen before. JEP 374 follows, in a way, JEP 312 from JDK 10, where thread-local handshakes were proposed to optimize the Hotspot safepoint mechanism. Note that certain Java applications may experience a drop in performance. That is why the community has left the option of re-enabling biased locking via the command line—to mitigate regression and get insight into when it might be useful nonetheless. Liberica JDK 15 also contains a custom feature: more root certificates since JEP 319 in JDK 10. BellSoft has added a certificate bundle from Mozilla CA Store, as Mozilla is the most trustworthy source of NSS root certificates. You can now download the Liberica JDK 15 builds here.
- [Java microservices: architecture, best practices, tutorials](https://bell-sw.com/announcements/2020/09/21/microservices-101-understanding-the-architecture/): There is hardly any developer left who hasn’t heard about the microservices. So much is written about microservice architecture, and yet you might still stumble through the basics. In this article, we give the definition of microservice architecture, discuss Java microservice frameworks, containers, native images, and the intricacies of creating and supporting microservices from a Java™ language perspective. You will find out how to turn your application into a working microservice. Intrigued? Dive right in! Table of Contents What are microservices? Java Microservice Architecture Contexts and Dependency Injection Dynamic proxies Web Server Java Virtual Machine Containerization Benefits of microservices Java microservices frameworks Spring Boot Jersey Micronaut Microservices best practices Additional tutorials What are microservices? The microservice architecture is a lightweight method of organizing software as opposed to monoliths. Previously, applications were mostly developed on a single codebase as centralized entities. Such an application had dozens of functionalities that processed tons of data. This in turn led to updating or troubleshooting being a difficult process: one tiny requirement made the team change the entire monolith. However, this style of software development was great for its purposes until applications became more prevalent on mobile devices and the cloud. When your back-end data is on different and numerous devices, monolithic architecture won’t cut it. This is where service-oriented architecture (SOA) comes in. SOA was an attempt to solve the downtime issue by dividing the structure into discrete units of similar functionality, i.e., services. Microservices are a variant of SOAs. Such services were designed to avoid the risk of software bloat. Microservices are smaller in size and communicate over a network through language-agnostic protocols, like APIs. It gives developers more freedom in choosing production tools since they don’t have to rely on enterprise service buses, other services, and the way they couple. Then, thanks to advances in containerization, parts of an application with microservices have become even more autonomous. Now business components of your application can be controlled individually while running simultaneously on the same hardware. Microservices in containers give way to cloud-native computing along with efficient and scalable applications. And if you use tiny containers built upon the inherently lightweight Alpine Linux, you will be able to reduce deployment time and cloud expenses significantly. Java Microservice Architecture If your team already has a monolithic application that grew out of its shoes, you can split it into a system of microservices. With time you can add new services that take off some load off the existing ones. Below you will find the tools that will help you to create microservice architecture in Java™. Contexts and Dependency Injection In Java EE, Contexts and Dependency Injection is a feature that helps to control the lifecycle of stateful objects using domain-specific contexts and integrate various components into client objects in a type-safe way. Annotations or various means of external configuration make it possible to animate plain class fields. Containers or frameworks that have control over class/bean lifecycle can inject necessary fields after constructing them in a sophisticated manner. Original code will not be affected by that complexity and will not be disrupted by a replacement for testing or changing the way of communication. The underlying principle of such architecture is called Inversion of Control (IoC). Dynamic proxies When we create service architecture, the role of reflection is critical. It powers discovery mechanisms, and components/beans find each other in run time, taking only required actions. In Java™, we can create a facade instance that intercepts interface method calls. This design pattern is called a Dynamic Proxy and is made with the help of java.lang.reflect.Proxy and java.lang.reflect.InvocationHandler. This is a built-in reflection API mechanism, widely used in CDI containers, e.g., in Spring. Web Server Frameworks allow the developers to plug in tunable components on a high level. But don’t forget about the low level programming and a whole world of lower-level communication protocols like HTTP where we need an intermediary between network connections and business logic. There’s also a need to manage some state (contexts), resources, and security. That’s what web servers do. Each microservice is coupled with its server instance configured in a centralized manner with the framework’s help, like Embedded Tomcat. Java Virtual Machine JVM and the core class library serve as a foundation for the web server, libraries, and business logic. The developer’s bytecode is verified and executed by the runtime and may be easily examined by standard diagnostic tools like Mission Control. JVM tuning is involved in the tuning of the web server, framework, libraries, and the application as well. As they typically allow customization, it’s natural to configure them all at once. In addition, JVM may be updated independently of business logic. So security updates, new features, and optimized defaults go into production with the main code intact. Containerization Containerization is a modern technology that makes all OS and external communications abstract without the downsides of hardware virtualization. Container management systems like Docker or Podman let us define, publish, and run container images with various software. Operating systems provide necessary levels of isolation and constrain resources for each container instance as requested by a management tool. Software components in a container image form a stack. The software stack makes physical deployment of a microservice more effective. When top levels are updated, we may reuse cached lower ones. Thus, binaries from cached layers aren’t transferred, and other preliminary actions do not perform. A layered image looks like this: The job of developers is to configure all these parts in such a way that they interact with one another, work correctly in a host OS, and communicate with external systems. Finally, there are orchestration systems that make it possible to distribute containers over a cluster of machines, like Kubernetes and Marathon. Here, traces of Java™ code and JVM disappear, building blocks become the configured containers deployed over the server fleet, and they may coexist with SaaS components available in a cloud. Benefits of microservices There are multiple instances where developers might find microservices quite useful. Scalability. Microservices are a perfect technology if you plan to scale your application up or down (vertically with CPUs/memory), out or in (horizontally with nodes/desktops/servers). Such flexibility is granted by each service being independent. Plus, it is possible to alter the application dynamically: turning components on and off to balance computational loads. Cloud-native apps. Microservices go well together with Docker containers, where they are encapsulated along with runtimes, frameworks and libraries, even an operating system. This way, every business function is separated, maintained, and deployed in the cloud as one unit. Undemanding maintenance. Smaller services are easier to test and debug. The microservice architecture is very straightforward: here’s an interface, its implementation, and a defined number of services. You can replace some of them and don’t touch the rest. Combined with continuous delivery, this design model drives you closer to delivering fail-proof products to your customers. High resiliency. Since an application is decoupled into independent services, it’s more resistant to errors. A bug in one part of the code will not cause the entire system to shut down. Instead, the team will only have to fix a service. Overall economy. A benefit in three parts: Building an application as individual services means short release cycles. An enterprise doesn’t need a complete redesign to modernize one service; it uses continuous delivery with cross-functional teams and deploys services independently. Containerized images with microservices use file systems such as UnionFS, AuFS, OverlayFS. They create layers that describe how to recreate actions done to a base image. As a result, only the number of layer updates increases, leaving containers lightweight, saving valuable resources and reducing company expenses. When functionalities are autonomous, you can assign separate teams to work on each. They don’t even have to be at the same place. In addition, your engineers will be able to deploy microservices with the simplest devices (read “x86 32 bit”) so you don’t need to equip yourself with pricey machines. A wide selection of tools. The microservice architecture helps to avoid vendor lock-in. If components don’t depend on one another, they may run on different frameworks or use a number of libraries. Developers might even choose several languages to write code for a single application. Independence from the database. When a service gets separated from the system, the database links get very flexible because of a unified interface. It is possible to make changes to the data or even replace the database entirely. Java microservices frameworks Frameworks are critical to implementing many routine tasks within microservices. They are very abundant in our industry, but they don’t exist in a vacuum and sometimes share common APIs. It is not uncommon for frameworks to support multiple APIs because they allow porting existing code as is or with minor changes. Below you will find the most useful frameworks for building microservices in Java, as well as sample code to launch your demo apps. Spring Boot Spring Boot, a part of an extensive Spring ecosystem, is the most popular framework in Java development. Spring Boot uses an embedded server Tomcat thus eliminating the need for Java EE containers. It also supports IoC, AOP, and has various projects for convenient microservices development, including Spring Data for microservices associated with data access; Spring Security for integrating authentication and authorization features; Spring Cloud for building distributed systems. In addition, the Spring ecosystem was recently enriched with Spring Native, a platform for native image construction. We will talk about the benefits of native images later, but for now, let’s create a sample microservice in Spring Boot. Starting with Spring Boot couldn’t be more convenient. Use a spring initializr to create a web server. Select Maven Project, the latest Spring Boot version and the Java version you are using. Add Spring Web dependency. Finally, download the project, open it in your favorite IDE, and add the following sample code to the DemoApplication class: import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @SpringBootApplication @RestController public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } @RequestMapping("/") public String hello() { return "Hi there!"; } } That’s it! You can launch the app, and it will listen on http://localhost:8080. Jersey Eclipse Jersey is a Java framework aiding in RESTful Web Services development. Jersey implements the JAX-RS API and makes the creation of RESTful services a lot easier. To get started with Jersey, study the documentation at the developer’s website. You can create a Maven project, run mvn archetype:generate -DarchetypeArtifactId=jersey-quickstart-grizzly2 \ -DarchetypeGroupId=org.glassfish.jersey.archetypes -DinteractiveMode=false \ -DgroupId=com.example -DartifactId=simple-service -Dpackage=com.example \ -DarchetypeVersion=2.35 This will generate a Jersey project on top of a Grizzly container, which you can mend as you see fit. Micronaut Micronaut is a freshman in the world of Java frameworks, but it was created especially for facilitating microservices development. It includes built-in cloud-native support and AOT compilation, and is compatible with several programming languages: Java, Kotlin, and Groovy. To start with Micronaut, create a demo application at micronaut.io/launch. Then, create a HelloController class and add import io.micronaut.http.MediaType; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Get; import io.micronaut.http.annotation.Produces; @Controller("/") public class HelloController { @Get @Produces(MediaType.TEXT_PLAIN) public String index() { return "Hello World!"; } } The application will answer with “Hello World!” at http://localhost:8080. Microservices best practices Microservice system can quickly spin out of control due to its complexity and turn into an intricate web that even senior developers will find hard to unweave. How to solve this problem? Decouple services with event streaming platforms (Kafka, Kinesis, Spark Streaming), simplify the service-to-service communication with a service mesh (Istio, OSM on Kubernetes), or redesign the system. Optimally, your goal is to avoid this: …by turning it into this: Note that the first scheme doesn’t feature any connection between message queues (represented by “MQ” cylinders) and databases. Also, if we look at the upper level, right below the user, we can see that it retains substantial complexity. Don’t forget that it’s rare to have only one application copy within a service. When they are numerous, it is necessary to understand where to address requests in case of failure. A service mesh network is meant to facilitate request delivery. Inside it, requests do not leave their individual infrastructure level, routed between microservices via proxies. Individual proxies here are called “sidecars” because they go alongside each service or application. The sidecar proxy design pattern is also used outside of microservices. It eases the tracking and maintenance of applications by abstracting particular features away from the central architecture: interservice communication, security, and monitoring. However, even when we have our microservices running, configured, and distributed, there is another issue that calls for our attention. How to minimize memory and time consumption? And here’s where we turn to the Native Image technology for a solution. First, how does Native Image work? Developers change a service binary form to a native executable with the help of GraalVM Native Image technology. A mix of Graal compiler applied in AOT mode and SubstrateVM virtual machine promises instant startup time, low memory consumption, short disk space requirements, and excellent performance. The native image runs Java™ microservices in closed-world assumptions. Base container level must provide minimal functionality (and there’s no JDK), which means it may be a “scratch” base image. It will work if a deployed application brings its dependencies: e.g., a native image may be statically linked with a standard C library. To facilitate the creation of native images, BellSoft developed a special tool, Liberica Native Image Kit. It allows creating microservices in different programming languages and supports a wide range of JDK versions and platforms so you can create an extensive network of microservices without worrying about compatibility. Discover Liberica NIK Running your Java™ project in a native image is obviously a more attractive choice in performance and speed. However, picking either runtime or native image for building the microservice architecture depends on the tasks at hand, business environment, the state of your enterprise, and many other aspects. Additional tutorials If you’d like to put theory into practice, you can build your own application using our series of Java microservices tutorials: Building a Java microservices application Deploying Java microservices Getting Java microservices ready for enterprise use We also prepared a set of tutorials on how to integrate Liberica Native Image Kit with popular Java frameworks: Creating microservices with Micronaut and Native Image Kit Integrating Quarkus with Native Image Kit Spring Native: a powerful addition to Spring ecosystem Microservices have the status of a young but promising technology. If you feel that your company is ready to disrupt the status quo and break up its monolithic golem, give microservices a try. And if you need expert help our Java professionals are ready to consult you on the matter. Contact our experts
- [Liberica JDK release accessibility convenience in developer and user scenarios](https://bell-sw.com/announcements/2020/09/30/Liberica-JDK-release-accessibility/): We at BellSoft emphasize that Liberica JDK must be convenient and accessible to all users. Our development team makes sure the runtime is available as Docker container images, on package managers, clouds, and other distribution channels. Working on various operating systems and cloud storage platforms suffers from JDK’s lengthy deployment and updating. We have designed a specialized API to deploy releases and update quickly and uniformly in different scenarios. Say, a server application v3.3.1 on Windows Server should always have JDK 8u242 installed while the client on any other operating system is running on the latest version. Another example is a slack bot informing of a recent Liberica update suitable for the project. Or configurators to choose JDKs or JREs — possibilities are endless here. Here in this article, we will provide a unified approach to obtaining the newest Liberica JDK versions and working with them in even the most primitive environments on virtually any operating system. The choice dilemma We thought of making some steps towards the ease of distribution after analyzing user behavior on the product website and finding out that visitors parsed it for download links from scripts or their programs. The decision was then to create a public API for everyone’s convenience. Determining the layout and form of the API took considerable time, even longer than actually designing it. Liberica JDK is a ‘broad’ product with several major versions, multiple supported architectures, installation options, and binary assemblies, all supported in parallel. Releases come out more often than four times a year, and the number of artifacts in each can be hundreds. 2020 has already seen five new releases. In January: versions 8u242, 11.0.6, and 13.0.2; in March: 14.0; in April: 8u252, 11.0.7, and 14.0.1; in July: 8u262, 8u265, 11.0.8, and 14.0.2; and in September: 15.0. A total of over 1,000 artifacts have been released. It is quite difficult to parse and analyze such a large amount of data fully; and if you selected a wrong binary, the consequences could be quite grave, from spending extra memory on functionality unused in the application to security problems. Package managers do not solve the issue, as they simply install specific versions or builds and do not help make the right choice. A good API should be able to answer the following user questions: I know what I want, would you please give me a download link and checksum? I have version XYZ installed, does it contain the latest fixes? Applications for the API The main application for the API in light of the different updating mechanisms is its versatility and the convenience it grants users in streamlining their work with Java. The API provides structured, filtered, up-to-date build information over networks in a unified manner. The API also assists in parsing HTML pages, a laborious process that takes up valuable development time. At its core, an API is a reliable contract that users can employ to build a stable software script with minimum time constraints and less hassle in searching for the needed JDK versions. There are three ‘ideal’ use cases for the API: The team needs notifications only (a ‘get updates’ case). This case is for, more often, companies using modern CI/CD pipelines and containers. So when a new Liberica JDK version appears on their radar, it first goes to developers as a base container image. It spreads to test environments, then deploys on a few machines and, finally, covers the whole production. A helpful addition to the API here is writing a bot that will instantly ping you when a new version is released. The team prefers ruled updates (a ‘get updates’ case). Say, at some point, a company decides to upgrade servers or desktops. If it uses a Linux package manager, the standard system update is triggered—that is rather simple. If not, or if the team works on Windows, there may be an agent that can perform or skip an update by a centralized command or when some conditions become true. In such an instance, it is crucial to verify the checksum also provided by the API to ensure that the downloaded artifact is exactly what BellSoft has built and tested. When a particular version is installed, the API clears up which download links are used for every operating system. The team uses or develops a package manager (a ‘get versions’ case). A private artifactory system may perform extra checks and provide DMZ access. But again, it is essential first to compare checksum pulled in binaries. There may be no actual file storage (but a proxy), and original links could be provided by an internal company API online or offline. A good example of the last one is how JetBrains use the API. Recently, IntelliJ IDEA has offered a possibility to install JDKs produced by numerous vendors, BellSoft included. Project settings -> SDKs -> + -> Download JDK… As illustrated, this dialog’s content comes from BellSoft’s API: IntelliJ IDEA is looking for the latest Liberica JDK (14.0.2 at the time of writing) and the latest LTS versions for JDK 8 and 11. We offer the API to guarantee that its URL and protocol contain the latest information. More importantly, the approach is well documented and complies fully with industry standards. Our engineers are always looking for ways to make the products better and welcome feedback from users. Such an approach becomes ever more productive in the API case for allowing them to update the API and improve user experience. Release The most important implementation aspect is the download link to acquire or update the JDK. For convenience, our team has selected the file name to be a separate native ID, thus allowing the user to check that the artifact was downloaded correctly. The next step is the Liberica JDK version. A version in the Java world is a rather capacious concept with an entire JEP 322 specification dedicated to it. But it only works from Java 10 onwards. For Java 9, you would refer to JEP 223. API use convenience This diversity leads to two main issues. The first is that the components of the versions in these specifications are named differently, leading to constant updates of Java 8, which has no such specifications. Besides, the notation in JEP 322 is less intuitive than in JEP 223: e.g., ‘feature’ instead of ‘major’, ‘interim’ instead of ‘minor’, etc. Based on the given facts, our development team always decided to use the new notation and decompose the Java 8 and 9 versions into new fields. In total, six more fields are added to the original object: "Version": "11.0.7+10", "featureVersion": 8, "interimVersion": 0, "updateVersion": 7, "patchVersion": 0, "buildVersion": 10 Next, numerous fields for describing the current artifact: operating system (os); architecture; bitness; installation type — archive or package for automatic deployment (installationType); package type — file extension in its essence (packageType); bundle type (bundleType). If the first five properties are obvious, the sixth requires some explanation. One version of Liberica JDK is released in three separate sub-versions (six in fact, because of JDK and JRE): Standard — regular JDK or JRE; Full — Standard, with JavaFX added; Lite — Standard, with specific features optimized by jlink. To ensure that all Liberica JDK’s users know which versions to choose, we introduced several assisting functions, namely: The LTS (Long term support) field — set to true for versions 8 and 11. The EOL (End of life) field — for Liberica JDK versions no longer updated (9, 10, 12, 13). The latestInFeatureVersion flag — indicates whether the artifact is the latest among feature versions. The latest flag — indicates whether the artifact is, in fact, the latest among all the existing versions. Below you will encounter some more abbreviations, which mean the following: GA — General Availability. Stable release. EA — Early Access. Unstable release, not shown in the examples here. FX — JavaFX is present. Editions glossary: jre — JRE, runtime. jdk — JDK is also a classic. JRE + development features. jdk-full — JDK + JavaFX; in fact, it also includes the Minimal VM for embedded solutions on platforms where relevant. jdk-lite — TODO. jre-full — JRE + JavaFX. Framework The Spring framework from Pivotal (BellSoft’s partner), running on Liberica JDK, was chosen for the API backend implementation. The server receives data for operation from the BellSoft GitHub repository. During development, it was decided to add separate endpoints. The API supports filters and returning text responses if users do not want to parse JSON strings. You can find detailed technical documentation on the Open API and try the API in action here. Several examples of its use: Find a link to the required artifact. Or get the link as plain text. 2.1. Check if a certain Liberica JDK version is the latest one available. The response should contain “latest”: true 2.2. Check if a certain Liberica JDK version is the most recent LTS version. The response should contain “latestLTS”: true Our developers plan to add all the company’s products, like Liberica Mission Control and others, to this API. User end Earlier, the article presented the three common use cases for the API. From this point forward, it features a case study of BellSoft’s API from a Liberica JDK user Vladimir Bogdanov, who works as a system administrator at Voronezh State Technical University. The team has specific requirements (a ‘get versions’ case). It frequently provisions new machines with a toolset using helpers like Ansible. API is very flexible here, e.g., it selects the latest LTS versions, and configuration is controlled in platform-independent and ‘write once’ style. The team also works in a physical hardware lab for educational and scientific purposes. It has about a dozen x86_64 machines running Windows. Frequently, a machine might break, or the OS might stop responding, or a new host might require setup. There is a defined set of configuration steps with software like IDEs and research tools installed, including JDK. BellSoft thanks Vladimir for his insights and an unusual application for the API. I first felt the need for adequate software and proper installation guides a decade ago, in 2010, when the school I work at brought in computers. Over time, the number of machines increased, as did the requirements for educational tools. On top of some run of the mill Autocad and Word, informatics teachers asked for 7th Delphi, VisualStudio, MinGW, as well as NetBeans and Eclipse (for the ‘Java-related purposes’). We managed this menagerie of applications under our own steam, as there was no alternative. Many different builds and incompatibility issues led to disarray and software’s improper functioning. Deployment was a mess; development times were staggering. These times bred vast numbers of add-ons and versions of the same programs. When after years of inconvenience, I stumbled upon BellSoft’s API, I was excited. It allowed working with the many JDK versions via a single access point. Identifying the latest versions of compatible software Given the added deb-based and rpm-based distributions, the main question ensuing from the short introduction is: How could I find out the latest stable LTS version for 64-bit Windows with an MSI installer on an ordinary (Intel or AMD) computer, containing JDK, JRE and JavaFX? The following is one of the methods: curl https://api.bell-sw.com/v1/liberica/releases?version-modifier=latest&bitness=64&release-type=lts&package-type=msi&bundle-type=jdk-full At the time of writing, this request returned “11.0.8”. The same is applicable for Windows PowerShell. Type in Invoke-RestMethod and insert the same link as above in the command line. { "bitness": 64, "buildVersion": 10, "EOL": false, "latestLTS": true, "os": "windows", "updateVersion": 8, "downloadUrl": "https://github.com/bell-sw/Liberica/releases/download/11.0.8+10/bellsoft-jdk11.0.8+10-windows-amd64-full.msi", "interimVersion": 0, "latestInFeatureVersion": true, "LTS": true, "bundleType": "jdk-full", "version": "11.0.8 + 10", "featureVersion": 11, "packageType": "msi", "sha1": "", "FX": true, "filename": "bellsoft-jdk11.0.8+10-windows-amd64-full.msi", "installationType": "installer", "size": 266248192, "patchVersion": 0, "GA": true, "architecture": "x86", "latest": false } curl --silent https://api.bell-sw.com/v1/liberica/releases downloads JSON without unnecessary details about speed, time and file size, and then uploads what was downloaded onto the console. Windows 10 has it out of the box on its official website. On Linux, you need to type in sudo apt \ yum \ dnf \ zypper \ apk -y and install curl depending on the distribution. "os": "windows" and "bitness": 64 are clear from looking at the one-liner and comparing it with the example above. To get a download link, perform the same operation but change featureVersion to downloadUrl: curl --silent "https://api.bell-sw.com/v1/liberica/releases?package-type=msi&version-modifier=latest&release-type=lts&bundle-type=jdk-full&bitness=64&fields=downloadUrl&output=text" Note that "featureVersion": 11 is the same as sudo apt-get install bellsoft-java11 from the Linux installation example. Thus, the string to install the latest LTS version on Ubuntu will look like this, after adding the repository, key and updating the indexes: LATEST_LTS_LIBERICA=$(curl "https://api.bell-sw.com/v1/liberica/releases?package-type=deb&version-modifier=latest&release-type=lts&bundle-type=jdk-full&bitness=64&arch=x86&fields=featureVersion&output=text") sudo apt -y install bellsoft-java"$LATEST_LTS_LIBERICA_JAVA" Ansible approach to installing JDK I have also published an Ansible role to install the necessary versions of JDK/JRE. As mentioned before, our lab has specific requirements for the API—a broad spectrum toolset, IDEs, and unstable hardware—so it is quite natural to use Ansible for this task. Repeat the same but adopting the syntax of your configuration management system (to obtain the release name): Variables: liberica_common_architecture: x86 liberica_common_bundletype: jdk-full liberica_common_eol: 'false' liberica_common_lts: 'true' liberica_common_ga: 'true' liberica_common_latestlts: 'true' Extract the link with information and write it to the liberica_releases variable. - name: Get releases info from API block: - uri: url: https://api.bell-sw.com/v1/liberica/releases method: GET return_content: yes delegate_to: localhost register: liberica_releases set_fact: liberica_version_number_fact: "{{liberica_java_version | default (liberica_releases.json | json_query (query_feature_version))}}" vars: query_feature_version: "[? architecture ==` {{liberica_common_architecture}} `] | [? bundleType == `{{liberica_common_bundletype}}`] | [? EOL == `{{liberica_common_eol}}`] | [? LTS == `{{liberica_common_lts}}`] | [? GA == `{{liberica_common_ga}}`] | [? latestLTS == `{{liberica_common_latestlts}}`] .featureVersion | [0] " Some more explanations: liberica_version_number_fact is the name of the variable that will contain the “11” mentioned before. liberica_java_version is the version that, in theory, the user should set if they need a particular one. If nothing is specified, the default, or everything inside default(), is executed. liberica_releases.json is the proprietary register: liberica_releases variable. Ansible understands if it is given JSON as a reference and puts it in a separate json key. json_query is a standard Ansible function. It is an overlay for jmespath. Note that it is not a regular BellSoft API query, but a query string for the jq tool used in this role as an alternative way of extracting information. Everything below query_feature_version: is the same as the preceding code, only split into several lines, because ansible-lint was not compatible. The double parentheses are part of jinja2’s templating and contain the variable name. Same as $VAR in bash. I should also mention the quotes. This bracing is specific only to jmespath. Conclusion User convenience is at the core of the API release from BellSoft, as multiple distributions are connected with issues related to their updating. Our development team aims to push its product this year and works hard to implement new even more user-friendly features. Liberica Download API is a universal solution that works for all the cases described in even the most primitive operating environment. It is available in IntelliJ IDEA or on our website. The many problems faced on different systems can be negated by installing a safe and unified Liberica software package to work with the JDK.
- [How BellSoft ensures
Liberica JDK quality](https://bell-sw.com/announcements/2020/10/19/How-Bellsoft-Ensures-Liberica-JDK-Quality/): We at BellSoft take nothing more seriously than the quality, security, and performance of our products. These are the most common concerns for any organization that chooses to develop with OpenJDK. No need to worry—you do not have to “only pick two.” In this material, we go over the main components that constitute Liberica JDK quality control (or QC for short). The goal is to explain how we work on the Quality–Security–Performance triangle, manage to attain a balance, and produce the best Java runtime we can. Check it out for yourself: click the button below to see all its benefits and Liberica binaries distributed entirely for free. DISCOVER LIBERICA JDK Release cycle Before we unravel our QC intricacies, let’s look into why we need it, exactly. OpenJDK Critical Patch Updates (CPUs), containing fixes to vulnerabilities, are date-driven and released quarterly. These are not insignificant; for instance, last year saw 1957 defects and 95 CVE fixed or backported in JDK 8 and 11 combined. In addition to that, major feature releases are developed in a 6-month cadence. Plus, all vendors customarily deliver bug fixes to customers before or after the next feature release. Now that we have described the existing state of affairs for OpenJDK in general, what about BellSoft specifically? What does our patch flow look like? As you can see, the process is somewhat confusing and has many actors. To reach eventual consistency with OpenJDK, to ensure all Liberica JDK releases are up to par, QC is simply imperative. Quality criteria for releases In 2020, the Java™ programming language celebrated its 25th anniversary and is still thriving. BellSoft can acclaim to meet the high standards to quality the industry has set over this time. Let’s review the main criteria for a compliant release. A Technology Compatibility Kit (TCK) is a set of tests provided by Oracle. They indicate if a particular Java™ implementation is compatible with Java SE specifications. This suite is the Swiss army knife of QC. It indicates that a Java™ platform is highly performant and will not damage your critical applications. All Liberica binaries have passed the TCK tests and are fully ratified in the Java Community Process. As for the numbers of tests in JDKs (Java Compatibility Kits) for the latest LTS releases and current 15, here they are: JCK-8c: over 126,000 JCK-11: over 139,000 JCK-15: over 152,000 One of the commonly used approaches to security analysis is proof-of-concept (PoC) exploits. They are non-harmful attacks meant to unveil software weaknesses. In our case, the engineering team develops a piece of PoC code to verify whether a particular OpenJDK vulnerability is exploitable. Once a security flaw is found, we provide a fix. This process is repeated for every release. Another part of quality control at BellSoft is functional OpenJDK tests executed by the Java Regression Test Harness (jtreg). It is used for functional and regression Java SE testing, which belongs strictly to a TCK. We run somewhere between 11,000 and 34,000 regression tests per binary before pushing a new Liberica JDK version, including hotspot, jdk, langtools, nashorn and, in certain cases, jcstress. A set of OpenJDK regression tests is also complemented by the largest industry frameworks: Spring and Apache Tomcat. During the most recent check, BellSoft performed more than 21,000 tests that are part of Spring and 40,000 from Tomcat (including APR connector tests running with Tomcat Native), which revealed no regressions. Benchmark testing is as essential as all the previous steps. There are two sides to this process. First, we evaluate Liberica JDK’s core functionality with major industry-standard benchmarks, such as SPECjbb and SPECjvm. Second, our team runs microbenchmarks embedded in the OpenJDK code. Based on the community-developed Java Microbenchmark Harness (JMH), they measure the performance of individual functions. How do we make it happen on time? Work on each release starts well in advance, sometimes more than a year before. It depends on the scope of work and helps us not rush the development. Key factors for timely quality control: push all test patches upstream, to the mainline JDK branch, as soon as they are developed; agree within the OpenJDK Vulnerability Group on the scope of each release. Since it is combined of engineers from major distributors such as Oracle, SAP, Amazon, and Red Hat, BellSoft has a clear understanding of timelines; test all platforms with and without security fixes; verify that final testing right before the release finds no new issues in the build but reconfirms its finished status; make sure binaries stored on CDN are not corrupt during network transfer (using checksums). The role of CI/CD pipelines An OpenJDK distributor does quite a lot of testing to a single build. And yet, this lot pales before the massive number of binaries to be checked before release. 12 platforms (Windows x86 32/64 bit, macOS, Linux x86 32/64 bit, Alpine Linux 64 bit/musl, Linux ARM 32/64 bit, Solaris Sparc/x86) 3 JVMs (server, client, minimal) JDK and JRE 7 types of installers: msi, pkg, dmg, deb, rpm, zip, tar.gz 3 presets: lite, standard, full Multiplied and with thousands of Java and JVM command-line options added, this impressive array is impossible to test manually. Strict guidelines that help with timing are great. But no amount of discipline beats automation. Continuous integration and continuous delivery (CI/CD) systems enter the scene. A way to implement a CI/CD system, or pipeline, is to use Jenkins as a core technology. It allows plugging in APIs, software libraries, and build tools. On its own, Jenkins does not have any functionality but gets powerful with other various tools. Groups of events or jobs are aligned together in a sequence. Automated Liberica JDK building and testing is Jenkins-based and is executed on nodes, computers with various OS and microprocessor architectures. Not all nodes are up simultaneously; they are only enabled when needed. BellSoft has a minimum amount of them on at all times for production, to fix own and customers’ bugs. At peak load during the release, we engage extra nodes, and the whole CI farm with cloud instances accounts for 400 to 1,200 nodes. CI/CD pipelines are one of the reasons we manage to finish Liberica JDK quality control on time. Final stages Above, we have discussed events happening several weeks before the release. Everything we do is to be sure that day X goes without a hitch. On the penultimate day, X-1, we prepare Liberica JDK bundles, make release notes, and push the builds to content delivery networks (CDNs) for a better user experience. Extra patches for customers Finally, open source JDKs would not evolve so rapidly if it was not for the community. BellSoft is among the top 5 OpenJDK contributors because of the number of fixes and proposals the company makes. However, we have ranked this high thanks to the work we do in cahoots with Liberica JDK users. By providing early patches and binaries for testing to our customers, we get to find solutions to known (and sometimes hidden) issues much faster. Furthermore, we send patches to functional issues before the next update release to expedite their resolution. Want to get valuable patches before the rest of the world knows about them? Contact our engineering team. We will examine your unique case to offer what is best for your enterprise. Learn how to make the most out of the high-quality Java runtime that is Liberica JDK. GET FREE ADVICE
- [JVM in Linux containers, surviving the isolation](https://bell-sw.com/announcements/2020/10/28/JVM-in-Linux-containers-surviving-the-isolation/): Running a Java application in Docker on a VM hosted in the cloud is not uncommon these days. But let’s take a closer look at this setup. We have a bare metal box somewhere in the cloud provider’s data center and hypervisor host OS running on that box. Next, we have a guest OS running in a VM provided by the hypervisor. Docker is running in the guest OS and provides a container runtime. Not to mention JVM in the container, which is also a type of VM. To sum it up, we have virtualization, container, and JVM all inside each other, stacked as a nesting doll. A big promise that container technologies give us developers is being able to control the environment for applications to run. Ideally, once packed in a Docker or Podman container, the application should behave the same regardless of where it started. Sometimes, things do not work as expected, and you have to learn the internals of Linux container technologies to fix problems. In this post, I would like to cover a few typical caveats of container resource management and networking with a view on JVM. Table of Contents Virtualization How are containers different from virtual machines? Why containers instead of VM? Why do I need to care about containers? Honoring the limits Russian roulette with oom killer Native memory tracking Networking VPN and the mystery of hanging connections Kernel level resource limits JMX in container Conclusion Related Posts Virtualization While this post is specifically about JVM and Linux containers, I would like to explain how containers are different from virtualization first. The key idea of virtualization is to execute normal binary code inside a VM retaining semantic of CPU architecture but controlling resource usage and device access. A security boundary around the VM is an essential part of virtualization. Today virtualization typically utilizes CPU support, which helps to create a low overhead sandbox for guest OS. This also means that host and guest OS’s should have the same CPU architecture. Virtualization is also possible without special hardware support by simulating the execution of CPU instructions in software. Simulation-based virtualization, while slower, is still practical for running guests having CPU architecture different from the host (e.g., running ARM-based Android OS on x86_64 host). JVM is also a simulator of sorts; it is running JVM bytecode on your host CPU architecture. And surprise, surprise, both JVM and modern CPU simulators are using JIT (Just in Time) compilation to improve simulation performance. JVM is not a real hypervisor, even if there are many parallels between JVM and virtualization hypervisor. How are containers different from virtual machines? Many people mistake containers for a kind of virtualization technology and form certain assumptions based on that misunderstanding. The major difference is that each VM on the hypervisor has a separate OS kernel (including own memory management, CPU scheduling, etc.). In contrast, all containers on the same host share the kernel between each other and the host. “Yet, I can run a RHEL container on my Ubuntu host, can’t I?” Yes, you can, although your RHEL would run with a kernel from the Ubuntu host (typically a few versions ahead). Thanks to Linux kernel stable contracts for syscalls, things just work even if userspace binaries were built for an older Linux kernel version (most of the time, at least). “Hey, but I can run Docker on my macOS. Where is the Linux kernel there?” It is there alright, just running using virtualization, and all your Docker containers are running in Linux VM silently sitting on your macOS. Containers are not a virtualization technology is an important fact to keep in mind. But what is a Linux container then? My casual answer is: Linux containers are just a glorified chroot. Virtualization creates VM boundaries by controlling the execution of CPU instructions; the container boundary in Linux is created by controlling kernel syscalls. Kernel syscalls surface is much wider, with multiple distinct Linux features rigged together to enforce container boundaries. Below are key features used for Linux containers. cgroups is an important feature used to control resource utilization for containers. CPU and memory limits in containers are enforced via cgroups. Dedicated FS mounted as root in the container. For Docker, image content is mounted as root, other containers (e.g., LXC) may use a full-fledged file system on block devices for this purpose. Namespaces are another feature critical for container isolation. This feature of the Linux kernel makes it possible to control kernel resource visibility at the process level. In particular, it is used to control individual FS mounts, network devices, and user accounts at the container level. Virtual ethernet adapters and NAT are used to create container network isolation. Typically, a container would have its own set of network adapters that are connected to the outside world via NAT. Container runtime needs to do a lot of plumbing and may make features above work together consistently to create an experience of container isolation. Why containers instead of VM? The main reason is efficiency. The process started in the container is just another process started at the host OS. There is virtually no overhead between containerized and non containerized process execution. Virtualization, even hardware-assisted, always brings a noticeable toll on resource utilization and startup time. Although virtualization is evolving and new optimizations are popping up, it would never become a zero-cost abstraction. Specifically, containers are very lightweight in terms of memory, while virtualization always urges you to have overhead for the copy of the kernel in each VM. Why do I need to care about containers? So we have a VM, inside a VM, inside a VM. “Why would I care?” you might ask. In an ideal world, you wouldn’t need to. Things just work there. However, ours is not an ideal world. JVM (Java Virtual Machine) is a VM. Despite it not being a VM as per virtualization terms above, it is still an abstraction layer between code inside JVM and outside OS. To build an abstraction efficiently, JVM needs to be highly aware of the OS it is running on. There is a lot of OS-specific code in the OpenJDK codebase, a requirement for Java code to execute properly. Containers are subtly challenging the status quo of how the OS behaves. And for JVM, a product with a long history, it is not always easy to adapt to the new rules of the game. Honoring the limits Memory and cpu limits are important features of containers. But they are controlled by cgroups and dangerously ignored by applications unless they have built-in support for cgroups. Try running docker run -m 512m ubuntu free on Linux console. Container memory is limited to 512 MiB by -m 512m option, though free command would report memory statistics for the host OS. Let’s now try a similar experiment with JVM and look at how it would restrict heap size depending on container size. \> docker run bellsoft/liberica-openjdk-debian:11 java -XX:+PrintFlagsFinal -version | grep -iE "InitialHeapSize|MaxHeapSize" size_t InitialHeapSize = 257949696 {product} {ergonomic} size_t MaxHeapSize = 4120903680 {product} {ergonomic} \> docker run -m 512m bellsoft/liberica-openjdk-debian:11 java -XX:+PrintFlagsFinal -version | grep -iE "InitialHeapSize|MaxHeapSize" size_t InitialHeapSize = 8388608 {product} {ergonomic} size_t MaxHeapSize = 134217728 {product} {ergonomic} It looks like JVM recognizes cgroups memory limit. With the growing popularity of solutions based on cgroups (Docker included), JVM has to rework its resource allocation heuristics. Special handling for cgroups was also added (see JDK-8146115). Changes were introduced in OpenJDK 10 and backported to Java 8. We can turn off this feature and see what happens. Ok, JVM can detect memory limit, but how value for max heap size is calculated anyway? MaxHeapSize now is calculated as -XX:MaxRAMPercentage * MEMORY_LIMIT where MEMORY_LIMIT is either cgroups limit or RAM available on the host machine. MaxRAMPercentage is 25% by default, which is probably too small for a container case. You can easily adjust it and still keep your JVM heap limit relative to the container memory limit. \> docker run -m 512m bellsoft/liberica-openjdk-debian:11 java -XX:MaxRAMPercentage=75 \ -XX:+PrintFlagsFinal -version | grep -iE "InitialHeapSize|MaxHeapSize" size_t InitialHeapSize = 8388608 {product} {ergonomic} size_t MaxHeapSize = 402653184 {product} {ergonomic} Besides memory limit, JVM acknowledges CPU limit and adjusts the number of GC threads based on container limits, too. -XX:+UseContainerSupport flag responsible for cgroups awareness is on by default in OpenJDK 11 and above and since OpenJDK 8u191. Russian roulette with oom killer Following the previous exercise, you may ask: “Why not set MaxRAMPercentage to 100%?” An important thing you should never forget regarding JVM memory is that heap size ≠ memory used. Without jumping straight into the rabbit hole of memory management, let’s play with container memory limits of container and JVM size and see what would happen. I have prepared a short code snippet to play with. import java.util.ArrayList; import java.util.List; public class HeapFiller { public static void main(String[] args) { Runtime runtime = Runtime.getRuntime(); System.out.println("Max heap size: " + (runtime.maxMemory() >> 20) + "M"); try { List data = new ArrayList<>(); long lastMemUse = runtime.totalMemory() - runtime.freeMemory(); while(true) { data.add(new byte[1 << 20]); long memUse = runtime.totalMemory() - runtime.freeMemory(); if (memUse > lastMemUse + (20 << 20)) { System.out.println("Heap usage: " + (memUse >> 20) + "M"); lastMemUse = memUse; } } } catch (OutOfMemoryError e) { System.out.println(e.toString()); runtime.halt(0); } } } Save code as HeapFiller.java and build the Docker image using an ordinary build file. FROM bellsoft/liberica-openjdk-debian:11 COPY *.java /app/ RUN cd /app && javac *.java Build the image. docker build -t testapp . Now let’s see what would happen with -XX:MaxRAMPercentage=100. /> docker run -m 512m testapp java -XX:MaxRAMPercentage=100 -cp /app HeapFiller Max heap size: 494M ... Heap usage: 465M Heap usage: 485M java.lang.OutOfMemoryError: Java heap space Looks fine, we have filled the whole heap and got OOME in our code. Let’s try to raise the heap size above the memory allowance of the container. /> docker run -m 512m testapp java -Xmx2g -cp /app HeapFiller Max heap size: 1979M ... Heap usage: 767M Heap usage: 787M Heap usage: 807M In this run, the JVM process is just killed. No “out of memory” or other errors, the container has simply disappeared. And still, if I run dmesg, the error is easily spotted. \> dmesg [97773.657826] oom-kill:constraint=CONSTRAINT_MEMCG,nodemask=(null),cpuset=80f0e7d30336357ce04b4dda36a72c79f0677913b9a5988357845e43cc7d5a54,mems_allowed=0,oom_memcg=/docker/80f0e7d30336357ce04b4dda36a72c79f0677913b9a5988357845e43cc7d5a54,task_memcg=/docker/80f0e7d30336357ce04b4dda36a72c79f0677913b9a5988357845e43cc7d5a54,task=java,pid=13413,uid=0 [97773.657872] Memory cgroup out of memory: Killed process 13413 (java) total-vm:4321312kB, anon-rss:518932kB, file-rss:19428kB, shmem-rss:0kB [97773.701215] oom_reaper: reaped process 13413 (java), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB [97773.816474] docker0: port 1(veth8303ede) entered disabled state [97773.817047] veth658eeeb: renamed from eth0 [97773.894689] docker0: port 1(veth8303ede) entered disabled state [97773.898459] device veth8303ede left promiscuous mode [97773.898465] docker0: port 1(veth8303ede) entered disabled state This is the way memory limits are enforced in containers. Breach the limit, and you are dead. No warnings, no second chances. But why did we get killed at 807MiB of heap usage (and why log shows anon-rss:519MiB)? cgroups can control both resident memory and swap memory utilization. Docker option -m 512m is interpreted as 512MiB of resident memory + 512MiB of swap memory (though only if swap is enabled in the system). So the effective limit was 1GiB. Speaking of swap, it is typically enabled on desktops and small VMs. However, servers with high amounts of RAM often have swap disabled (e.g., most images on AWS except the smallest ones have swap disabled). Your container can run locally but crash being deployed on the target environment due to lack of swap. We can control the swap limit directly. Now it’s time to disallow swap usage. Combination -m 512m --memory-swap 512m will restrict total memory usage to 512MiB. \> docker run -m 512m --memory-swap 512m testapp java -Xmx2g -cp /app HeapFiller Max heap size: 1979M ... Heap usage: 445M Heap usage: 465M Heap usage: 485M Now the container is killed near its expected limit. Let’s try -XX:MaxRAMPercentage=100 again. \> docker run -m 512m --memory-swap 512m testapp java -XX:MaxRAMPercentage=100 -cp /app HeapFiller Max heap size: 494M ... Heap usage: 446M Heap usage: 466M Heap usage: 486M java.lang.OutOfMemoryError: Java heap space We got a JVM level error now. This application is very simple, though, just one thread and tiny code size. The example below both creates threads and fills the heap with objects import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Exchanger; import java.util.concurrent.Executor; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class ThreadSpawner { static CountDownLatch LATCH = new CountDownLatch(1); public static void main(String[] args) { Runtime runtime = Runtime.getRuntime(); int recursionDepth = Integer.getInteger("depth", 128); try { Executor pool = new ThreadPoolExecutor(Integer.MAX_VALUE, Integer.MAX_VALUE, 1, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10)); long lastThreadCount = Thread.activeCount(); while(true) { Exchanger ex = new Exchanger<>(); pool.execute(() -> recursiveTask(recursionDepth, "", ex)); ex.exchange(""); if (Thread.activeCount() > lastThreadCount + 50) { long memUse = runtime.totalMemory() - runtime.freeMemory(); System.out.print("Heap usage: " + (memUse >> 20) + "M"); System.out.println(" | Thread count: " + Thread.activeCount()); lastThreadCount = Thread.activeCount(); } } } catch (InterruptedException e) { System.out.println("Interrupted"); } catch (OutOfMemoryError e) { System.out.println(e.toString()); Runtime.getRuntime().halt(0); } } public static void recursiveTask(int depth, String input, Exchanger receiver) { try { if (depth == 0) { receiver.exchange(input); // block thread LATCH.await(); } else { recursiveTask(depth - 1, input + String.valueOf(new byte[10 << 10]), receiver); // prevent GC from collecting variable before the call input.length(); } } catch (InterruptedException e) { System.out.println("Interrupted"); } catch (OutOfMemoryError e) { System.out.println(e.toString()); Runtime.getRuntime().halt(0); } } } Save code as ThreadSpawner.java and rebuild the Docker image. Now let’s try to run it with 512MiB of memory limit. \> docker run -m 512m --memory-swap 512m testapp java -XX:MaxRAMPercentage=100 -cp /app ThreadSpawner ... Heap usage: 43M | Thread count: 409 Heap usage: 60M | Thread count: 460 Heap usage: 59M | Thread count: 511 With lots of threads, the container is killed much earlier reaching just 62MiB of heap utilization. It should surprise you at this point. Each thread requires a stack occupied outside of the heap. Can we reduce stack size? The default stack size is 1024 KiB. If we cut it in half, the container should survive more threads. \> docker run -m 512m --memory-swap 512m testapp java -XX:MaxRAMPercentage=100 -XX:ThreadStackSize=512 -cp /app ThreadSpawner ... Heap usage: 45M | Thread count: 409 Heap usage: 44M | Thread count: 460 Heap usage: 62M | Thread count: 511 No effect. The container has survived the same number of threads. Instead of tweaking JVM options, let’s change recursion depth in the application code. With option -Ddepth=64 recursion depth is code snippet would be limited to 64 instead of 128. \> docker run -m 512m --memory-swap 512m testapp java -XX:MaxRAMPercentage=100 -Ddepth=64 -cp /app ThreadSpawner ... Heap usage: 31M | Thread count: 919 Heap usage: 28M | Thread count: 970 Heap usage: 38M | Thread count: 1021 Now the container can survive roughly two times more threads. The reason for such behavior is that memory for thread stack is allocated lazily. JVM reserves address space for stack, but real memory allocation happens only then the memory page (usually 4 KiB) is touched by the app. JVM has other memory areas apart from heap. Even though they are not as large as heap typically, they have to be accounted for too. Key JVM memory areas are Heap - our plain Java objects are stored here, heap size can be limited with -Xmx option. Metaspace - holds class-related data structures, grows with more classes being loaded. Metaspace can be limited with -XX:MaxMetaspaceSize option. Threads - each live thread needs a stack. Individual stack size can be tuned with -XX:ThreadStackSize, but the number of threads is driven by application. Direct memory - non-heap memory available via java.nio package. Direct buffer memory pool can be limited by -XX:MaxDirectMemorySize, the default limit is equal to the max heap size. Code cache - used for binaries of JIT-compiled code. -XX:ReservedCodeCacheSize option controls maximum memory used for code cache. This is not a full list. Native memory tracking, covered later in this post, could be enabled to get a detailed memory report from the JVM runtime. Memory related things quickly become insanely complex if you are trying to build an accurate picture. Do you need to know all this just to run your web app in a container? Probably not. Allow me to summarize the takeaways. Containers have separate limits for resident memory and resident + swap memory used by the process. Different container runtimes use different ways to set this limit. JVM scales itself based on the container resident memory limit. Although the default factor is 25%, which is too small for a dedicated Java application container. -XX:MaxRAMPercentage=75 would set the factor to 75%, a more sensible starting point in that instance. JVM scales only heap space. You need to keep in mind other areas too. Pay special attention to the number of threads, especially in small containers. Once the container memory limit is breached, do not expect any exception within JVM or shutdown hooks execution, it just gets terminated. Discover recommendations on tuning the JVM for resource constrained containers in a dedicated guide. Native memory tracking Native memory tracking is a JVM feature that can help you understand your memory budget including both heap and non-heap memory spaces. Native memory tracking should be enabled on the JVM command line via -XX:NativeMemoryTracking=detail flag. Later you could use jcmd to dump memory usage reports from JVM. An example of a summary level report is below. \> jcmd 12345 VM.native_memory summary Native Memory Tracking: Total: reserved=1822547KB, committed=62203KB - Java Heap (reserved=393216KB, committed=24676KB) (mmap: reserved=393216KB, committed=24676KB) - Class (reserved=1056947KB, committed=5811KB) (classes \#735) ( instance classes \#640, array classes \#95) (malloc=179KB \#1340) (mmap: reserved=1056768KB, committed=5632KB) ( Metadata: ) ( reserved=8192KB, committed=5120KB) ( used=4825KB) ( free=295KB) ( waste=0KB =0.00%) ( Class space:) ( reserved=1048576KB, committed=512KB) ( used=454KB) ( free=58KB) ( waste=0KB =0.00%) - Thread (reserved=115662KB, committed=16334KB) (thread \#112) (stack: reserved=115128KB, committed=15800KB) (malloc=405KB \#674) (arena=129KB \#222) - Code (reserved=247772KB, committed=7632KB) (malloc=84KB \#741) (mmap: reserved=247688KB, committed=7548KB) - GC (reserved=1333KB, committed=133KB) (malloc=45KB \#159) (mmap: reserved=1288KB, committed=88KB) - Compiler (reserved=165KB, committed=165KB) (malloc=32KB \#68) (arena=133KB \#5) - Internal (reserved=4674KB, committed=4674KB) (malloc=4642KB \#1679) (mmap: reserved=32KB, committed=32KB) - Symbol (reserved=2055KB, committed=2055KB) (malloc=1247KB \#2917) (arena=807KB \#1) - Native Memory Tracking (reserved=385KB, committed=385KB) (malloc=187KB \#2643) (tracking overhead=198KB) - Arena Chunk (reserved=188KB, committed=188KB) (malloc=188KB) - Logging (reserved=4KB, committed=4KB) (malloc=4KB \#191) - Arguments (reserved=18KB, committed=18KB) (malloc=18KB \#476) - Module (reserved=59KB, committed=59KB) (malloc=59KB \#1027) - Synchronizer (reserved=63KB, committed=63KB) (malloc=63KB \#494) - Safepoint (reserved=8KB, committed=8KB) (mmap: reserved=8KB, committed=8KB) You can replace summary with detail in the command above to get an even more detailed report. JVM can also calculate memory usage difference. Use jcmd PID VM.native_memory baseline to capture baseline. Later use jcmd PID VM.native_memory summary.diff or jcmd PID VM.native_memory detail.diff to get a report of changes in memory usage. Native memory tracking is useful for investigating abnormal memory usage by JVM, but it does cover only JVM memory areas. There is still libc heap used by JVM or other native code loaded into the process, which is not accounted for by this report. Networking Networking is another part of the container isolation boundary. A container has its own loopback adapter (127.0.0.1) and one or more virtual Ethernet adapters to speak to the rest of the world. A virtual network interface of a container is typically NATed through the host’s network interface—not only an option but the most common one. NAT (Network Address Translation) is a widely used technology. But it is introduced in a very unexpected place with containers. VPN and the mystery of hanging connections One issue with NAT is the inability to process fragmented IP packets (in the case of port-based NAT). Again fragmented IP packets are rare on the network, but a combination of VPN and NAT on the same host machine (e.g., developers desktop) could introduce funny network issues. VPN may have a lower MTU than normal network and break packets into fragments. Small pieces of data would pass smoothly between an application in the container and service on the other side of VPN. Yet, once the size of data reaches a certain limit, connection hangs forever. Docker has an option to disable network isolation, which helps to work around such problems. Kernel level resource limits As the kernel is shared across all containers, certain global limits are applied to all containers too. Containers tend to be small; so, a good server could be stuffed with lots of them and exhaust these limits. The effect might be very obscure: say, the container is running fine on one node but experiencing network issues on another. JMX in container JMX is a TCP based protocol used by JVM to communicate with diagnostic tools. JMX is an ancient protocol based on RMI (Remote Method Invocation) protocol endemic to the Java world. On the JVM side, you need to add options to start listening on a particular port. Then remote tools such as Mission Control can connect to this port and start talking to the JVM. If you wonder why you would want to use Mission Control, take a look at my first article about JDK Flight Recorder. Due to the idiosyncrasies of a JMX protocol, using it in containers becomes a challenge. Above there is a sequence diagram of the JMX connection. A problematic piece is a serialized stub of a remote object with an IP address and a port the second connection would use. Below is an example of JVM options to listen to the JMX protocol on port 55555. With Docker, you would need to add port mapping with option -p 55555 to expose this port starting a container. JMX config options Connection diagram -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.port=55555 -Dcom.sun.management.jmxremote.rmi.port=5555 One way to fix it is to override the address inside the stub. JMX config options Connection diagram -Djava.rmi.server.hostname= -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.port=55555 -Dcom.sun.management.jmxremote.rmi.port=5555 This being one solution, it will not work if you cannot connect to the container’s host directly or if the port cannot be exposed with the same number. In other words, Kubernetes would need to solve the problem differently. Another solution requires forwarding. JMX config options Connection diagram -Djava.rmi.server.hostname=127.0.0.1 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.port=55555 -Dcom.sun.management.jmxremote.rmi.port=5555 Such a setup would work with SSH tunnels and Kubernetes port forwarding. Conclusion Containers solve a lot of problems related to the deployment of complex applications. Unfortunately, they also bring in a bunch of new ones. When things work, containers are great. But both software vendors and application developers have to be aware of runtime considerations specific to Linux containers. The early years of container adoption were pretty rough for JVM, as many old configuration heuristics were failing miserably in containerized environments. Thanks to the work done by OpenJDK supporters, numerous issues have been addressed. Now OpenJDK is well prepared to run in containers. Related Posts JDK Flight Recorder – a gem hidden in OpenJDK Hunting down code hotspots with JDK Flight Recorder Hunting down memory issues with JDK Flight Recorder JDK Flight Recorder, The Programmatic Way
- [TeXnical Writing Part 1:
Foundations](https://bell-sw.com/announcements/2020/11/02/TeXnical-Writing-Part-1-Foundations/): Introduction Plain text documents are timeless. The earliest ASCII documents ever written can still be opened, read, and modified today, on virtually any hardware, using any operating system, without any proprietary software, online service, or third-party conversion program. In stark contrast are document formats such as those produced by Microsoft Word, ClarisWorks, Lotus Manuscript, WordPerfect, WordStar, and most other word processors, whether discontinued or not. A double-edged sword to some proprietary word processors is their ability to show the document’s final form while editing. This ability makes tying a document’s appearance to its content easy, which can lead to troubles later on when attempting to change either independently. Before 1978, typesetting mathematics was expensive, laborious, or aesthetically displeasing. Eventually, a small subset of word processing software offered features to typeset formulas and equations (e.g., Microsoft Word 6.0 with Equation Editor 1.0 released in the early 1990s). While this addressed some issues, proprietary file formats remained problematic. In 1978, Donald Knuth released an open specification for typesetting mathematics using a plain text syntax called TeX (pronounced “tech”). Back then computers were not fast enough to render TeX in real-time, making it impossible to see the final form while actively editing the document. Modern laptops are powerful enough to typeset TeX in real-time, decreasing the amount of time spent waiting for documents to build. Software developers, scientists, engineers, and mathematicians all use mathematics to communicate algorithms, formulas, and equations succinctly and accurately. As technology-based societies expand, we need tools to help people express complex ideas without ties to proprietary technology. In other words, we’d like a workflow that gives us the ability to write plain text documents that may be published to any medium, such as web or print, using cross-platform software. In this series, we’ll develop a Java-based desktop application using JavaFX that converts mathematical formulas written in Markdown documents into HTML documents. We’ll conquer technical challenges using techniques that allow us to achieve the real-time conversion from Markdown to HTML. Each article in the series builds upon the previous one. Broadly, the articles include: Foundation — Create a new project, develop a text editor, and build an executable binary. Markdown — Update the text editor to support Markdown syntax, and provide a preview panel to show the result. Syntax — Add syntax highlighting to the text editor. Math — Include a TeX engine that can draw mathematical formulas on a JavaFX canvas or as scalable vector graphics, such as the following equation: Performance — Profile the application to attain real-time rendering of hundreds of formulas. To develop the editor, we’ll use Liberica JDK for a few reasons. First, it comes bundled with JavaFX, which is a feature-rich library for writing cross-platform desktop applications. Second, the way the download URLs are designed makes it super-easy to write installation scripts that target specific Java versions. Audience Readers are expected to have a working knowledge of software design patterns, the Java programming language, and be familiar with web-related technologies such as HTML and CSS. Basic knowledge of TeX is useful but not necessary. Readers should understand basic development operations, such as configuring environment variables. Although the instructions are written for a Unix-like platform (e.g., Linux), the principles behind them are applicable to any operating system. For Windows users, consider one of the following solutions: Install Cygwin Install the Windows Subsystem for Linux Install VirtualBox along with a Linux distribution Software requirements Download the latest versions of the following software packages, preferably in .zip or .tar.gz format where possible: Liberica JDK (full version) from BellSoft, such as shown in the following figure: Gradle from Gradle Inc. IntelliJ IDEA from JetBrains s.r.o. For the purposes of these instructions, we’ll refer to the download location as $(logname)/downloads, which references the non-administrative account name. On Windows, this would be equivalent to %USERNAME%\Downloads. Development environment setup This section describes the installation steps to configure the system for development. There are many ways to set up a software development environment. Installing third-party applications into /opt then leveraging symbolic links makes upgrading and downgrading a simple matter of replacing a symbolic link. This approach can work on Windows but requires special software to create directory links. Liberica JDK Broadly, we want to install Liberica JDK into a common directory, set up the JAVA_HOME environment variable, update the system PATH variable, then verify that Java is installed. Accomplish this by completing the following steps to install Java system-wide: Open a terminal. Change to the root user (e.g., sudo su -). Extract Liberica JDK using the following commands (change the archive filename as appropriate): cd /opt tar xf $(logname)/downloads/bellsoft-jdk15+36-linux-amd64-full.tar.gz Create a symbolic link having a name that is independent of the Java version installed: ln -s jdk-15-full jdk Log out of the root account. Java is now installed into /opt/jdk. Next, configure the environment variables for a non-administrative account as follows: Edit the system environment variables (e.g., $HOME/.bashrc). Append the following lines: export JAVA_HOME=/opt/jdk export IDEA_HOME=/opt/idea export GRADLE_HOME=/opt/gradle export PATH="$PATH:$HOME/bin:$JAVA_HOME/bin:$IDEA_HOME/bin:$GRADLE_HOME/bin" Save the file. Open a new terminal (you may close the previous one). Verify that Java is installed: java -version If successful, the console will display a result similar to the following output: openjdk version "15" 2020-09-15 OpenJDK Runtime Environment (build 15+36) OpenJDK 64-Bit Server VM (build 15+36, mixed mode, sharing) Java is installed. Advantages to this installation approach include: full control over the exact version of Java being installed without regards to availability by package managers; the environment variables never have to change value; switching between versions entails changing a single symbolic link; and we can ensure that the only place Java binaries and supporting libraries are located is under /opt. The main disadvantage is that some packages cannot detect that Java is installed, despite both JAVA_HOME being set and the java command being available via the PATH. There are other ways to install Java on your system, such as using a package manager. Note that you must configure the package manager to download the full version of Liberica JDK. Gradle Gradle is a build system we’ll use to simplify compiling Java source files into bytecode then bundling our bytecode and third-party libraries into a single JAR file. Install Gradle using similar steps to installing Java, as follows: Open a terminal, if not already opened. Change to the root user (e.g., sudo su -). Extract Gradle using the following commands (be sure to substitute the version number as appropriate): cd /opt unzip $(logname)/downloads/gradle-6.6.1-bin.zip Create a symbolic link as follows: ln -s gradle-6.6.1 gradle Log out of the root account. Verify that Gradle is installed: gradle -version If all went well, the console will display a result that begins with the following output, showing the version number: ------------------------------------------------------------ Gradle 6.6.1 ------------------------------------------------------------ Gradle is installed. IntelliJ IDEA Repeat similar steps to installing Java and Gradle for installing the integrated development environment (IDE): Open a terminal, if not already opened. Change to the root user (e.g., sudo su -). Extract IntelliJ IDEA using the following commands (substitute the version number as appropriate): cd /opt tar xf $(logname)/downloads/ideaIC-2020.2.2.tar.gz Create a symbolic link as follows: ln -s idea-IC-202.7319.50 idea Log out of the root account. IntelliJ IDEA is installed. Project environment setup For this project, we’re going to create a non-modular application built using Gradle. After Gradle is configured, we’ll import the project into the IDE and then begin development. Initialization Initialize the project as follows: mkdir -p $HOME/dev/java/mdtexfx cd $HOME/dev/java/mdtexfx gradle init Next, Gradle will ask a few questions; when prompted, provide the following answers: Choose 2 to build an application. Choose 3 to set the language to Java. Choose 1 to set the build script to use Groovy. Choose 4 to test using JUnit Jupiter (a.k.a. JUnit 5). Press Enter to accept a Project name of mdtexfx. Set Source package to com.mdtexfx Gradle will create the following directory structure: . ├── gradle │ └── wrapper └── src ├── main │ ├── java │ │ └── com │ │ └── mdtexfx │ └── resources └── test ├── java │ └── com │ └── mdtexfx └── resources The project is initialized, along with the following source files: App.java — contains the main application entry point; and AppTest.java — helps verify that the code in App.java is correct. Build and then run the project by typing the following commands into the terminal: ./gradlew clean build ./gradlew run The output from Gradle will contain: > Task :run Hello world. This demonstrates that we’ve successfully initialized a rudimentary Gradle application. Open project Confirm that the application can run within the IDE as follows: Start IntelliJ IDEA. If no project is currently open: Click Open or Import. Open $HOME/dev/java/mdtexfx. Otherwise, if a project is already open: Choose File → New → Project from Existing Sources. Browse to $HOME/dev/java/mdtexfx. Click OK. Select Gradle. Click Finish. Click Run → Run. Select App. The same “Hello world” output message appears, which indicates that the IDE can run the application. Synchronize with build script IntelliJ IDEA may issue false warnings or report errors that are not necessarily correct; keeping the IDE in sync with the latest changes to the Gradle configuration file can resolve erroneous warnings. Accomplish this using the following steps, from within the IDE: Open build.gradle. Optionally, remove or replace the header comment at the top of the file. Click the Load Gradle Changes icon. The IDE reloads the Gradle configuration file. Configure Gradle Now that IntelliJ IDEA can run the application, we can proceed with configuring the build script for a simple JavaFX editor. Our Gradle build scripts use Groovy syntax, though other languages are possible—like Kotlin. This entails the following coarse-grained steps: Apply a JavaFX plugin Configure the JavaFX plugin Update dependencies Create an überjar Run the application An überjar is a Java archive (.jar) file that contains all class files and resources necessary to run the application. Apply JavaFX plugin JavaFX requires native binaries for each target platform. The JavaFX plugin simplifies the work of including native binaries for a specific platform. Update the plugins section to include the JavaFX plugin: // Apply the JavaFX plugin to add support for JavaFX. id 'org.openjfx.javafxplugin' version '0.0.9' Configure JavaFX plugin After the repositories section, configure the JavaFX plugin as follows: javafx { version = '15' modules = ['javafx.controls'] configuration = 'compileOnly' } Setting the configuration option to compileOnly instructs the JavaFX plugin that the native binaries will be provided separately. This is where Liberica JDK shines because it bundles the necessary JavaFX components with its full version of the OpenJDK. Setting the modules indicates that the application requires basic user interface functionality. This includes the concept of a scene, which is fundamental for creating a user interface in JavaFX. The version setting instructs the plugin to use modules for the given JavaFX version. Update dependencies Inside the dependencies section, insert the following code: runtimeOnly "org.openjfx:javafx-controls:15:linux" Adding the given dependency tells Gradle to download the necessary native binaries for running the application on Linux. As the application grows in complexity, we’ll need to include additional JavaFX dependencies. Create an überjar After the application section but before the test section, add the following code: jar { manifest { attributes 'Main-Class': application.mainClassName } from { (configurations.runtimeClasspath).collect { it.isDirectory() ? it : zipTree( it ) } } { exclude 'META-INF/*.RSA', 'META-INF/*.SF', 'META-INF/*.DSA' } } By default, Gradle will create a Java archive filename using the project name (i.e., mdtexfx.jar). To customize the filename, set archiveFileName to the desired value. Setting manifest allows the application’s main entry point to be executed by passing a -jar argument when running the Java virtual machine, which we’ll see shortly. The from section instructs Gradle to combine all the Java archive files required to run the application into a single file—the überjar. Without this step, launching the application would otherwise require passing a classpath argument to the Java virtual machine that includes paths to all the JAR files necessary to run the application. Excluding *.RSA, *.SF, and *.DSA removes all signature files from the final überjar. This precaution ensures that developers can sign the final Java archive file without conflicting with existing signature files. If .pom files are present, we may have to exclude those as well. Run application First, build the application’s Java archive (JAR) file through the IDE as follows: Click Gradle, found on the right-hand side. Expand mdtexfx → Tasks → build. Double-click jar. The application is built. Next, run the application as follows: Open a terminal. Change to the project directory (i.e., $HOME/dev/java/mdtexfx). Type: java -jar build/libs/mdtexfx.jar The console shows: Hello world. An application JAR file is built. Text editor Implement a minimal text editor as follows: In the IDE, expand mdtexfx → src → main → java → com.mdtexfx. Double-click App to open it. Replace the contents with the following program: package com.mdtexfx; import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.control.TextArea; import javafx.scene.layout.Pane; import javafx.stage.Stage;
public class App extends Application { @Override public void start( final Stage stage ) { final var editor = new TextArea(); final var scene = new Scene( editor ); stage.setScene( scene ); stage.show(); } } Notice that our main class (App) inherits from the JavaFX class named Application. The Application class provides the scaffolding necessary to launch a standalone JavaFX program. Next, the start method is called by the Application’s launcher to wire together the components that make up our program, including: TextArea — Graphical user interface element for text editing. Scene — Container for one or more user interface elements. Stage — Top-level container, typically the application window. JavaFX uses a theatre analogy: there is only one Stage; a Stage can have many Scenes, but only one is “performed” (active) at a time; each Scene contains JavaFX widgets—such as a TextArea—that are called nodes, and they represent the visual elements displayed, much like tangible objects in a theatrical performance. Calling stage.show() causes the application’s window to open, revealing the scene, which contains a single user interface element: the text area. From the terminal, rebuild and run the application as follows: ./gradlew clean build java -jar build/libs/mdtexfx.jar The following window appears: Click into the window to give the text area focus, type some text, then close the window. The application starts, accepts input, and can be closed. Unit test Changing App.java broke the unit test in AppTest.java. Before continuing, it is good practice to make sure the unit test passes. For our purposes to this point, if the application can be instantiated without failure, we’ll consider the test to have passed. Change AppTest.java as follows: class AppTest {
@Test void test_Instantiation_NewInstance_Created() { new App(); } } The unit test now passes. (See Roy Osherove’s unit test naming standards.) Executable binaries Building a JavaFX application for multiple platforms normally requires running jlink and jpackage to produce binaries. The problem is that that process must be run on each target platform. This means that to build a binary for OSX, the developer must own OSX. It is unreasonable to expect developers or small companies to run their build processes on every target platform. Instead, let’s create a build process for JavaFX that can target any platform from any platform—much like cross-compiling. To build standalone binaries for each target platform, we’ll use: Liberica JDK web API; and warp, a cross-platform application packager. Install warp before continuing. Build binary Building a self-contained Linux executable entails a few steps: Prepare a full Java runtime environment Create a launcher script Package the application into a standalone binary Prepare Java runtime environment Download then extract a 64-bit Linux Java runtime environment (JRE) as follows: Open a terminal. Change to the application directory (e.g., $HOME/dev/java/mdtexfx). Download and extract Liberica JDK into dist, such as: wget -q https://download.bell-sw.com/java/15+36/bellsoft-jre15+36-linux-amd64-full.tar.gz mkdir dist cd dist tar xf ../bellsoft*linux*tar.gz mv jre-15-full jre cd .. The JRE is extracted into $HOME/dev/java/mdtexfx/dist/jre. Create launcher script Create a launcher script responsible for running the application after it is extracted out of the standalone binary. Edit a new file named dist/run.sh, then insert the following contents: #!/usr/bin/env bash readonly SCRIPT_SRC="$(dirname "${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}")" "${SCRIPT_SRC}/jre/bin/java" -jar "${SCRIPT_SRC}/${paths.app}.jar" "$@" 2>&1 >/dev/null & Save the file, then finish up by making it executable using chmod +x dist/run.sh. Package application The Java Software Development Kit includes jlink and jpackage. These programs help eliminate unused dependencies when creating a JavaFX application, shrinking the size of the production executable. Unfortunately, they cannot be used to cross-build native binaries. That is, Linux versions cannot produce a standalone executable that runs on Windows, or vice-versa. One of Java’s core draws is its ability to create cross-platform software. Since JavaFX is no longer bundled with Java, operating system-specific libraries must be bundled into the Java archive file. This means creating separate native binaries for each target platform. Ideally, we’d be able to create all the native binaries from a single computer, regardless of the operating system. We can, but not with jlink and jpackage. Instead, package the application into a standalone binary using the warp-packer program as follows: ./gradlew clean build mv build/libs/mdtexfx.jar dist warp-packer \ --arch linux-x64 \ --input_dir dist \ --exec run.sh \ --output mdtexfx.bin Verify the standalone executable displays the application’s text editor by running it as follows: ./mdtexfx.bin Summary We’ve created a standalone binary for a JavaFX application, which can be cross-compiled to multiple platforms. The next article will build upon these foundational concepts to develop a Markdown text editor and a preview pane. Related posts TeXnical Writing Part 2: Markdown TeXnical Writing Part 3: Syntax TeXnical Writing Part 4: Math TeXnical Writing Part 5: Performance
- [BellSoft Kicks Off Bundled Offer with Karakun for Better Software Security and Simpler Migration](https://bell-sw.com/announcements/2020/11/13/BellSoft-Kicks-Off-Bundled-Offer-with-Karakun/): BellSoft, one of the top-5 OpenJDK contributors, and Karakun AG, a software engineering company that specializes in the development of custom solutions and maintainer of OpenWebStart, an open source re-implementation of the Java Web Start technology, have recently announced a new partnership. BellSoft and Karakun collaborate to provide a safe path from Oracle Java to open source solutions. With their high expertise, both companies can offer the market, which has seen a shift towards free and supported OpenJDK-based deployments, a new product: Liberica JDK with OpenWebStart, a multifaceted and, most importantly, reliable alternative to Oracle JDK with simple migration services and minimal risk. A study on “Global Java Web Frameworks Software Market 2020–2028” names security risks to data storage a major problem for the IT industry. In line with this, both BellSoft and Karakun focus on software security, put great emphasis on the ease of switching, and ensure that their products are exceptionally reliable. “We at BellSoft develop advanced software, accumulating the best Java practices into Liberica JDK. But Java’s future does not lie in constant innovation alone. Meeting users’ needs and guaranteeing their security are what drive our actions. We want to work with companies that also treat these aspects as the utmost priority. Hence, our strategic partnership with Karakun evolved from joint work within the Java Community, highly concerned with usability, convenience, and stability. We can guarantee that customers will benefit from a seamless migration process. As we strive to provide our customers with flawless security capabilities, BellSoft is involved in creating standard specifications for Java technology under the JCP program. This year, our representatives are nominated as Candidates for the Executive Committee,” says Alex Belokrylov, CEO of BellSoft. After software security, the ease of migration is another vitally important factor. As surveys show, nearly half of Oracle JDK customers require quality cost-effective solutions with releases coming in time and ready for production. Many enterprise developers need Java Web Start (JWS), a one-click deployment solution for Java applications on the desktop. Oracle JDK supported JWS until it was deprecated in Java 9 and subsequently removed from Java 11. Karakun and BellSoft will provide expert migration services to assist companies in switching from Oracle JDK to 100% open source solutions: reliable Liberica JDK with open source OpenWebStart in one package. Besides, German-speaking customers will take advantage of first line support in German. “Besides the open source character of Java represented by OpenJDK, it is very important for us to offer professional commercial support for such a crucial component like the JVM. Our cooperation with BellSoft provides an ideal and flexible bundle for all interested groups—either with OpenWebStart or with the classic JDK,” says Stephan Huber, COO of Karakun AG. Karakun now cooperates with BellSoft to deliver a fully-fledged, regularly updated, and thus secure Liberica JDK with OpenWebStart. This new complex product will help businesses to achieve the following: migrate from Oracle Java to Liberica JDK safely and fast; find new opportunities if they rely on WebStart / JNLP; reduce software and information security risk; stay with long-term support (LTS) releases; cut down enterprise costs for licensing; get first line support for Liberica JDK German-speaking customers. Confirmations and additional information: JVM Ecosystem Report 2020 Global Java Web Frameworks Software Market 2020–2028 Download the latest JDK 15 Request support for Liberica JDK with OpenWebStart Executive Committee of Java Community Process About BellSoft: BellSoft releases and supports Liberica JDK, an OpenJDK binary distribution verified by TCK for Java SE Standard Compliance. BellSoft engineers have been contributing to the OpenJDK project since its inception. Being one of the top-5 OpenJDK contributors, BellSoft helps effectively solve business problems in the server, cloud, and IoT applications of Java technologies. The company drives a community-powered approach to deliver reliable and compact containers with Liberica JDK targets microservice solutions on. Using popular as well as in-house performance optimization techniques and tools allow BellSoft to work on performance tuning for industry leaders. It also enables the company to provide customers with the software that meets their current and future business challenges, all based on the best Java expertise. bell-sw.com About Karakun AG: Karakun is the home of 50 highly qualified individuals, with 40 very experienced software and UX experts among them. Karakun uses agile methods to develop custom-tailored solutions for companies and organizations, based on Java and web technologies. Karakun employees are very active members in numerous open source projects. As knowledge transfer is an important part of Karakun’s DNA, the know-how and ideas resulting from this community work are being used in customer projects. To increase the level of standardization and to maximize efficiency during development, Karakun provides own software platforms for dedicated use cases. karakun.com
- [An intelligent bet for Kotlin
Part 1](https://bell-sw.com/announcements/2020/11/16/An-intelligent-bet-for-Kotlin-part-one/): The Java industry is moving forward along with the world around it. And software companies, as part of its community, are very much in tune with the trends. Progressive and novel things often emerge from technology crossovers. Here’s why BellSoft, determined to keep you informed of everything Java-related, has invited me to make a two-parter on the young and advanced Kotlin language. If you’re a Software Developer or any kind of professional working in the IT sector, you’ve probably heard of it. Kotlin has absorbed all the best features from other languages, while keeping full Java interoperability. Among the benefits, its supporters enumerate conciseness, expressiveness, simplicity. A very flat learning curve for Java developers is also a fact to consider for some organisations before deciding if they should switch to Kotlin. In the first article, I’ll be going through Kotlin’s most useful characteristics, like object destructuring from JS, and type interference, and many more. Here you’ll see its areas of application and how the language makes the developers’ lives easier when writing code compared to others. But more importantly, in the second part, we’ll discuss async programming with coroutines, observe how the language impacts the world now and even look into its future! You will basically learn everything you need to apply Kotlin to your systems. Its history Kotlin was first released in 2011 by the company JetBrains. If you don’t know about them, they were first known for creating the popular IntelliJ IDEA. Now they’re also very well-known as the creators of the Kotlin language. Although their first release was in 2011, it wasn’t until 2016 when Kotlin v1.0 came out, the first official release from which JetBrains will provide backwards compatibility. According to different sources, JetBrains engineers started writing their own language primarily due to not finding any that fulfils their needs. The goal was to build a concise, elegant and expressive language also able to be compiled fast. Another direction is crafting code into a domain-specific language (DSL) specialized for certain parts in an app. Instead of using design patterns and building unmanageable architectural solutions from scratch, Kotlin is here to help. It features numerous applications: you can basically write your own language without parsing software, IDEs, etc. It allows replacing long chains of factory methods and code duplication with readable constructions without any external tool. Writing a new language is not easy, but writing a new language with full interoperability with an existing language like Java is even more difficult. However, despite the challenges, JetBrains seems to be doing a very good job so far, seeing how warm a welcome their language has received from the community. Applications The foundation is that Kotlin is super versatile. Regardless of its comparatively young age, it’s an archetype for generating benchmark tests with JMH on par with much more “mature” Java, Groovy and Scala. Kotlin is good for low-level profiling to assess the performance and scalability of the systems. Here’s a custom example of Kotlin-based microbenchmark: @BenchmarkMode(Mode.Throughput) // Benchmark mode, using overall throughput mode. open class MyBenchmark{ @Benchmark //Just like in Java. fun testMethod(): Int { return sequenceOf(1..10).flatten() .map{ it * 2 } .filter { it % 3 == 0 } .map{ it+1 } .sum() } } Another direction is crafting code into a domain-specific language (DSL) specialized for certain parts in an app. Instead of using design patterns and building unmanageable architectural solutions from scratch, Kotlin is here to help. Replace long chains of factory methods and code duplication with readable constructions without any external tool. Or how about configuring Gradle builds? Also possible. There’s an official plugin from JetBrains to compile Kotlin code to target JVM (apply plugin: "kotlin”), JavaScript (apply plugin: "kotlin2js”), or Android: buildscript { ext.kotlin_version = '' ... dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } apply plugin: 'com.android.application' apply plugin: 'kotlin-android' As you can see, just over four years, Kotlin has become a multi-purpose ecosystem in itself. With it, you can write code, create microservices, generate JUnit tests, build microbenchmarks, and even assemble projects (which Java can’t do). Why is Kotlin becoming so popular and widely adopted in our industry? Let’s go through its key features to understand the reasons behind its popularity.1 Simplicity and pragmatism above all If there’s one thing that characterises Kotlin, this is its focus on pleasing developers’ wishes, an obsession in making their lives and their day-to-day work as smooth and simple as possible. When we write code daily at a professional level, it’s the small and apparently unimportant things that we face very often that kill our productivity. They produce a constant sense of waste of time and demotivation in us. Kotlin has removed quite a few unnecessary annoyances, very well-known and recognised by all, although for one reason or another nothing had been done until now. Now I’ll cover some of them briefly. Multiplatform Kotlin is a multiplatform language which can be executed in different supported platforms. You can target different runtime environments like JVM, Android, JS or many others with the use of Kotlin/JVM, Kotlin for Android, Kotlin/JS and Kotlin/Native. Kotlin Multiplatform, JetBrains’ open source code sharing SDK, is proven to accelerate development time across several teams who work on the same application for Android, iOS and web, up to 30%.2 It allows using a single business logic code in cross-platform software and basically taking advantage of one engineering team instead of two or three. Such a solution brings significant savings (in terms of both money and time), fewer bugs and QA processes, higher efficiency. To achieve all these benefits, JetBrains have their progressive runtime supported by BellSoft. In 2019, the two companies entered a partnership: BellSoft started providing updates and security patches in the same way it supports its own Liberica JDK. Due to Kotlin code being 100% Java-interoperable, I would say that Liberica JDK is the runtime for Kotlin. However, keep in mind that the status of the Multiplatform project is still in the alpha phase. It’s not stable yet, and you could expect some migration issues. Types Kotlin types are very similar to Java (numbers, characters, booleans, arrays and strings) with one main difference: primitives do not exist in Kotlin. Although primitives can’t be explicitly defined, Kotlin’s compiler will translate types into primitives in the JVM bytecode every time that it’s possible to favour a lower memory footprint. Creating a new instance Some languages like Java or C++ require the use of the “new” operator to create a new instance of an object. We’re all used to it, but is it indispensable? Why not just call the constructor? This is precisely what Kotlin does. We no longer need to use “new” in Kotlin, calling an object’s constructor is enough to create a new instance. For example, we can create an instance of an Order object like this: val order = Order(“id”, 10, BigDecimal(12.5), OrderType.BUY) End of statements For instance, why are we forced to write a semicolon at the end of each line in some languages? Providing the ability to write multiple statements in one single line is unnecessary and possibly not recommended for readability purposes, so why not accept a break line as the standard to represent the end of a statement? If you’re a Java or C++ developer, you’ve probably missed a semicolon thousands of times. Think about all the time wasted. Kotlin doesn’t require using a semicolon to close a statement, although if you do it, it will still compile. Type inference and immutability Kotlin also reduces the verbosity of having to declare the type of variables twice. Type inference allows us just to declare the creation of a new variable, inferring the type from the right-hand side part of the statement. Another improvement in variable declarations is that we don’t have to use any additional modifier to declare a variable as immutable. Kotlin provides just two keywords: var and val. Much simpler than in other languages, if you need a mutable variable use var; if you need an immutable variable use val, as simple as that. For instance, the following snippet would be allowed. We can override the value of a mutable variable defined with “var”. var order: Order = Order("id1", 10, BigDecimal(12.5), OrderType.BUY) order = Order("id2", 12, BigDecimal(15.5), OrderType.SELL) On the other hand, this situation won’t be allowed, as “order” is a immutable variable: val immutableOrder: Order = Order("id1", 10, BigDecimal(12.5), OrderType.BUY) immutableOrder = Order("id2", 12, BigDecimal(15.5), OrderType.SELL) //Compilation error! Data classes Kotlin provides a very neat and concise way of defining immutable objects. Here they’re called “data classes”. Declaring a class with immutable fields has always been specially over-complicated and verbose in Java, while in Kotlin you can achieve this in a single line: data class Employee(val id: String, val name: String, val age: Int) {} The NullPointerException dilemma If you’re a Java developer, you’ve definitely suffered the issues of having null as a possible value for an object or variable. What would be the simplest solution to this problem? Common sense tells us that not allowing nulls in the first place would probably be the simplest and safest solution; and that’s exactly what Kotlin does. If you try to assign a null value to an object in Kotlin, you’ll get a compilation error. So, by default, objects are not nullable, you’d have to explicitly tell the compiler that the object is nullable to allow nulls. That’s great, isn’t it? Once more, simplicity and safety first. val order: Order = null //Compilation error! val nullableOrder: Order? = null String interpolation I bet you’ve missed this feature hundreds of times. If you use a language with no support for string interpolation, having to concatenate different parts of a string to build the expected format is quite cumbersome. Kotlin provides a straightforward and fine way to do string interpolation. For instance, you could do this: val name = "John" val surname = "Smith" println("My name is $name $surname") Object destructuring Kotlin brings to us one of the coolest features available in JavaScript, object destructuring. We can do something like this: val (name, age) = Employee(“id1”, “John”, 35) Looks great, although unfortunately, this can only be done with data classes. If we wanted to use string interpolation without data classes, we are forced to implement component1() and component2() operators in our Order object, something that I’m personally not very happy with. For instance, if our Employee class weren’t a data class, we would have to do the following to be able to use object destructuring: class Employee(val id: String, val name: String, val age: Int) { operator fun component1(): String = name operator fun component2(): Int = age } You could also use Kotlin extensions to implement the required operators for the Employee class: private operator fun Employee.component1(): String = name private operator fun Employee.component2(): Int = age You must be wondering why we have to do that to be able to use object destructuring. The actual reason is that Kotlin compiler compiles a data class to the following if you decompile the corresponding class file: // IntelliJ API Decompiler stub source generated from a class file // Implementation of methods is not available package com.coding.kotlin public final data class Employee public constructor(id: kotlin.String, name: kotlin.String, age: kotlin.Int) { public final val age: kotlin.Int /* compiled code */ public final val id: kotlin.String /* compiled code */ public final val name: kotlin.String /* compiled code */ public final operator fun component1(): kotlin.String { /* compiled code */ } public final operator fun component2(): kotlin.String { /* compiled code */ } public final operator fun component3(): kotlin.Int { /* compiled code */ } } You can see that Kotlin provides a component operator for each property defined in the data class. However, if you modify Employee class not to be a data class, just a simple Kotlin class, then you will see that Kotlin doesn’t override those operators. // IntelliJ API Decompiler stub source generated from a class file // Implementation of methods is not available package com.coding.kotlin public final class Employee public constructor(id: kotlin.String, name: kotlin.String, age: kotlin.Int) { public final val age: kotlin.Int /* compiled code */ public final val id: kotlin.String /* compiled code */ public final val name: kotlin.String /* compiled code */ } One thing that Kotlin’s object destructuring solves nicely is the complex and messy manner that Java provides to iterate through the entries in a Map. In Kotlin we can simply do the following: for ((key, value) in myMap) { println("$key, $value") } This is definitely much more pleasant and still as fast! Default arguments In some languages, we have to duplicate constructors to allow the optionality of some of its arguments, something that adds clutter to our classes. Kotlin allows the option of providing default values for our arguments when they’re not specified. For instance, if we define a default value for our order type: constructor(userId: String, quantity: Int, price: BigDecimal, type: OrderType = OrderType.BUY) We can now instantiate an Order object without specifying the order type: val buyOrder = Order("id", 10, BigDecimal(13.2)) Named arguments If you’ve worked with complex domains with a considerable number of fields, it’s often difficult to keep track of what argument in the constructor corresponds to what field in the component. Kotlin solves this by providing the ability to name arguments in the constructor, this allows a more explicit declaration of arguments in a constructor, improving its readability considerably. Also, we wouldn’t have to place arguments in the same order in this case. For example: val sellOrder = Order("id", type = OrderType.SELL, price = BigDecimal(3.0), quantity = 1) In general, we have less rigidity and a more expressive and readable language. Async programming - Coroutines and Channels Kotlin takes a different approach to languages like Java to run multi-threaded applications. The way Kotlin works is very similar to what Golang does.3 Golang provides its goroutines and channels in the same way that Kotlin provides coroutines and channels. Kotlin coroutines are very lightweight components used to run tasks within a given scope. You can think of channels as pipes used to communicate between coroutines to achieve a common goal safely. The main benefit of Kotlin’s approach is that writing asynchronous code becomes much simpler. We’ll go through the reasons behind it in our next article in the series. Kotlin’s coroutines and channels are definitely a fascinating topic that probably deserves a sole article, but we’ll leave it here for the time being in this introductory piece on Kotlin. I’m going to give a more extended introduction to them in Part 2 of this small series. One last thing to mention is that Kotlin has some handy libraries to help us develop asynchronous applications, such as Ktor, an asynchronous framework for microservices and web applications in general.4 We could definitely go on for much longer showing all the existing features that Kotlin has come up with. But this is probably enough to give you a good idea of the radical change of mindset that this language has brought to the JVM world. A language design driven by developers, focused on productivity, simplicity and comfortability to make us enjoy what we do. A battle against boilerplate code and unnecessary clutter. This is the kind of pragmatism that Kotlin is aiming for, minuscule things that may seem insignificant by themselves. But when put together, they make a huge difference in terms of productivity! Tune in next time… As you could see after this introduction to Kotlin language, it’s a language designed with pragmatism and conciseness always in mind. Instead of reinventing the wheel with a completely new language, its developers have basically taken the nicest features from different languages in order to build one of the most beloved languages nowadays — a very sensible approach that guarantees success. Why make up something new when you already know what people love in other languages? The result is a language that keeps all the good parts of Java but simultaneously brings a more flexible and pragmatic approach that makes our day-to-day work as developers much more enjoyable. If you ask me, I think that JetBrains’ team has demonstrated that they know what developers like and what they don’t, a knowledge probably acquired from their experience building the most popular IDE. In my next article, we’ll be digging in a bit more into a few more interesting Kotlin features and discuss how Kotlin is currently doing in the market. We’ll also be talking about one very exciting aspect: what future lies ahead for Kotlin as a programming language. References Kotlin Language Guide. How Kotlin Multiplatform helps reduce app development time. The Go Programming Language. Ktor: Build Asynchronous Servers and Clients in Kotlin. Related posts An intelligent bet for Kotlin. Part 2
- [An intelligent
bet for Kotlin.
Part 2](https://bell-sw.com/announcements/2020/12/04/An-intelligent-bet-for-Kotlin-part-two/): The promising Kotlin language has made many developers jump for joy and even rekindled their interest in studying new ways in programming. Staying on top of all things IT, we like to look into what’s working now and how it can benefit the whole community. In the first part of this series, we went through some of Kotlin’s main features, remarking the conciseness, simplicity and pragmatism as bright signs of its identity. We touched upon its various uses, showcasing Kotlin’s versatility and how it provides plenty of options for developers. Throughout this article, we will discuss some of these features more extensively to have a better understanding of the real benefits that Kotlin could bring. We’re going to see the current impact of Kotlin on the modern industry and what we can expect in the future. Kotlin and Java: more on features Let’s dive now into a more in-depth comparison between Kotlin and Java to learn its current state and some of the problems that it might solve. If you check Kotlin’s releases page 1, you’ll see that it is on version 1.4. So how are Kotlin and Java releases juxtaposed? Are they similar or has something changed recently? Let’s compare their release cycles in the next timelines. As you could see in the first timeline, after JDK 9, JDK switched from a feature-based release cycle to a date-based one, with a new release every six months independently of the features’ state. In addition to that, a new LTS version is released every three years. Kotlin releases also first came out on a feature basis, until JetBrains decided to switch to a date-driven cycle starting from version 1.5 planned for spring 2021. It means that from 2021, both languages will be making more frequent releases regardless of whether new features are ready or still in progress. If we look closely at the language features, we can notice that Kotlin has supported from the very beginning most of the features that Java is currently trying to bring onboard. Another aspect is that Kotlin now seems to be stable and mature as a language: its releases are more focused on improving performance and developer experience. Java seems to be slowly introducing some features that Kotlin included since its early days; something that I’m calling the “Kotlinization of Java.” After looking at its main features last time, we could also see that some characteristics are similar to Java. A short learning curve makes Kotlin a good choice for Java developers. Although these two languages are often close, Kotlin repurposes certain nice features like object destructuring from JavaScript or coroutines and channels from Golang, becoming an enhanced programming language by taking all the right parts from different sources. So, the timelines affirm that Kotlin is paving the way in many aspects, as it has a better understanding of what developers demand from their primary language. In the first part of this series, I briefly mentioned Kotlin’s coroutines and its different approach for asynchronous programming with respect to Java. Let’s go through it more extensively to understand the big improvements that it brings. Async programming - Coroutines and Channels I’ve already mentioned this topic previously, but here we will try to understand why coroutines are so significant. First, let’s discuss the way Java threads work now. The current implementation of Java threads relies on the OS kernel threads. Two challenges arise: Threads are very heavy. Mainly because when a thread is suspended, both its native and Java’s call stacks have to be stored or retrieved. The OS scheduler can’t differentiate threads to group them per user or unit of work. Two threads processing the same data will most likely be executed by different CPUs. As a result, context switching between threads is expensive and degrades performance due to the high costs of transferring data between CPUs. Ideally, we’d want those two related threads to be processed by the same CPU, but that’s impossible using the OS scheduler. That brings us to Kotlin’s solution to this problem: coroutines. Kotlin’s coroutines are lightweight components used to run tasks concurrently. Compared to Java Threads, they are tiny in size and managed by Kotlin’s runtime rather than by the operating system. It means we can create lots and have a “thread” per user or transaction, in contrast to Java and its limitations. How about a coroutines example to understand them better: val start = System.nanoTime() runBlocking(Dispatchers.Default) { (1..2_000_000).map { index -> launch { delay(100) println("I'm a lightweight thread number $index! running on ${Thread.currentThread()}") } } } println("Done in ${(System.nanoTime() - start) / 10e09} seconds!") In this example, we are creating 2 million “threads,” each simulating a delay of 100 milliseconds. They will run concurrently, handled by Kotlin’s runtime. Kotlin allows a huge number of threads with no problem at all. This is absolutely not doable in Java. You’d need a limited size thread pool and reuse threads for different invocations as they’re expensive to create and to keep in memory. Running the code above, we can see that it completes successfully and the heap usage is quite reasonable considering that we’re instantiating 2 million threads: If we take a look at the actual threads running on our JVM, we can see that despite having 2 million virtual threads, the runtime is using a pool of eight threads or dispatchers. The way coroutines work simplifies writing asynchronous tasks for developers considerably. First, thread management is hidden behind Kotlin’s runtime, and second, you can write async tasks the same way you do synchronous ones. The language also provides channels as a way of communication between coroutines. Imagine them as a messaging system: An emitter sends messages to a channel, while one or more receivers process those messages. Coroutines always within a channel. It’s a much more efficient and more straightforward approach to deal with concurrency and parallelism. Instead of sharing resources, the stakeholders communicate between them to achieve a common goal! Now that we have a better knowledge of Kotlin as a language let’s see how it is currently doing in our industry. Its present, a really good start Judging from the market trends and developers’ comments on social media, the community has received Kotlin with a warm welcome. This language has been a breath of fresh air for many; its radical and intelligent approach has made a big difference. The image below shows the worldwide interest in Kotlin, according to Google Trends: Kotlin looks like a completely new language but at the same time has full Java-interoperability and is able to use the JVM as its runtime environment: this is a huge advantage and a smart move for its creators. Among different JVM languages capable of interoperating with Java code, Kotlin’s flawless interoperability is far superior in some cases. When the level of interoperability is similar, other characteristics tilt the balance in favour of Kotlin. Most enterprises are afraid of big changes. Understandably, they don’t want to get stuck in an uncomfortable situation where every employee dislikes the novelty, the management regrets the decision entirely, and no one sees an easy way of going back. Kotlin has allowed companies to take small steps, keep using the same libraries and frameworks and slowly move to Kotlin. They can see how things go following a more conservative approach, with no rush and minimal risk. Although Kotlin allows switching from Java in a smoother manner, it’s worth mentioning that transitioning to a new language in production is never an easy task. You’ll need to set up an appropriate plan with clearly defined goals. It’s crucial to understand: what we want to achieve, why we are making a choice we’re making, and how to take everyone on board, including the management. Think about what may hinder the migration. These obstacles include a potential lack of experience with the new language or difficulties in bringing new developers into your team. Once the risks are clear, it’ll be easier to prepare, minimise them or even have a backup plan ready. One important aspect to consider when migrating to Kotlin is that it has become a first-class citizen in the Spring framework after Pivotal (now part of VMware Tanzu) decided to start supporting Kotlin since version 5.0 in 2017.2 This allows using Kotlin features in the Spring integration and experiencing a simpler transition from Java for all projects using Spring. Another big step in Kotlin’s adoption has been its tremendous success in the mobile app industry. In 2017, Google first announced the Kotlin support for Android development. Then, only two years later, the IT giant publicly stated that its new preferred language for Android development was Kotlin. It means that now everyone can use the same language and the same libraries for backend and mobile applications as we do with Java, but writing a more concise, expressive and elegant code, while also taking advantage of the brilliant language features such as null-safety or coroutines. As we can see, Kotlin’s present looks bright, but what future lies ahead? Its future If someone made me bet for one language in the next few years, it’d definitely be Kotlin. Visionary language In our sector, being agile and fast to stay ahead is key to achieving business goals. That’s why betting for Kotlin could become a competitive advantage. The adoption of Kotlin could mean a boost in productivity for the teams in your organisation. Why wait for the next Java LTS release to be able to use some features which are already available in Kotlin? To make things worse, some of the features that we’ve been really missing for years in Java have been supported by Kotlin for years. While they are not even planned in Java’s roadmap for the next releases. Good examples of these missing features could be Java records: still feature previews, meaning we’ll have to wait at least 6–12 months before using them safely. Or the much-awaited Project Loom. Java Threads have been a pain for a very long time, and still, the work only started in late 2017. Although the OpenJDK community is making good progress and has early-access builds3 available, the project is still far from complete. In contrast, JetBrains managed to have something production-ready in a much more reasonable timeframe. This is one of the reasons why some developers lose their patience with Java and switch to Kotlin. When writing code in Kotlin, we get all the system benefits of Java as a JVM language, e.g. in terms of performance. Simultaneously, we benefit from one of the nicest syntaxes when it comes to readability. Usability We all know that Kotlin’s main uses are server-side and Android development. But what makes Kotlin even more interesting is that it’s been designed as a multiplatform language in a way that goes beyond our traditional understanding of “multiplatformity” from a JVM-world point of view. We can make use of Kotlin/JS to transpile our code to JavaScript, being able to write web frontend applications or even React applications!4 Without going into too much detail, I think that this could bring many benefits to full-stack development and, above all, fill an existing backend–frontend gap and improve things looking forward. Besides, we have Kotlin/Native to write Kotlin applications on the platforms where running a JVM is not possible or desirable (embedded devices, iOS, etc.) — which may be valuable to specific use cases. All of this provides increased flexibility for developers, with the added extra of fantastic tooling support provided by JetBrains, a major tooling manufacturer. Creators and community But the reasons behind a promising future for Kotlin are not purely technical; they’re also structural and organisational. From my perspective, JetBrains is a fairly small, dynamic and fast-paced company with clear objectives and huge ambitions. It is able to gain the right traction and enough speed to make things happen quickly, which is something big enterprises find very hard to beat. It might be their size that makes them slower or fear of the new, but they can’t compete with JetBrains. What I see happening in the near future is that Kotlin will keep coming up with features and innovation that will improve the language experience even further. The more developers embrace it in the Open Source community, the faster changes in the JVM world will take place. JetBrains has proven with their success with IntelliJ IDEA, becoming the favourite IDE among developers worldwide, that they understand developers and their needs. It is and will be the cornerstone in their success with Kotlin as well; know your audience, and they’ll love your product. We’ve also seen that the number of companies interested in Kotlin or partnering with JetBrains has been growing in the last few years. For instance, BellSoft and JetBrains agreed a strategic collaboration in 2019, in which Bellsoft provides security patches and critical updates to JetBrains runtime, a fork of JRE used in all language IDEs. These are the same updates and the same process as for BellSoft’s own distribution called Liberica JDK. That’s one of the reasons why I’d recommend using Liberica JDK with Kotlin due to their close relationship and collaboration. I can only see this kind of trend of mutual collaborations become more active and intense in the following years. Conclusion In summary, the future of Kotlin will depend on how willing and open-minded the community is to bet for this language firmly. Considering its simplicity and full interoperability with Java, I don’t see an apparent reason not to give it a chance. Kotlin is a language able to improve a team’s productivity and the readability of the codebase. On top of that, it allows migrating gradually and taking minimal risk. In terms of growth, the dynamic and fast-paced environment provided by JetBrains could be a critical factor in the upcoming years. The company knows developers’ needs and has proven that by building the most popular IDE. This vital experience, combined with a clear determination, might be a decisive factor for a big success of Kotlin in the long run. Who knows, it might be time to break with the past and bet for the future after all! References Kotlin Releases. Introducing Kotlin support in Spring Framework 5.0. Project Loom Early-Access Builds. Get started with Kotlin/JS for React. Related posts An intelligent bet for Kotlin. Part 1
- [TeXnical Writing Part 2:
Markdown](https://bell-sw.com/announcements/2020/12/10/TeXnical-Writing-Part-2-Markdown/): Welcome back to developing a Liberica JDK-based app for real-time conversion of mathematical formulas from Markdown to HTML. If the first part of this series was all about building an executable binary, the second one focuses on building a text editor that supports Markdown syntax and a preview pane to show the result. Introduction In The Pragmatic Programmer, Hunt and Thomas offer a fundamental software development principle: "Every piece of knowledge must have a single, unambiguous, authoritative representation within a system." Often touted as the DRY principle (don’t repeat yourself), I liken the principle to computers abhor exceptions. That is, introducing differences adds complexity, which compounds over time, leading to systems where the burden of maintenance exceeds the value returned from development efforts. Systems that are easy to maintain implement functionality in general terms rather than hard-code specific behaviour. Well-crafted software minimizes complexity by identifying generalizations of major components before development begins. Many general-purpose solutions to common design problems can be found in Design Patterns by Gamma et al. Markdown is a specific instance of a plain text markup syntax that provides formatting hints for a computer to use when presenting a document. Software editors for drafting and previewing plain text documents typically support a single markup format, rather than the general case. Other plain text markup formats include AsciiDoc, MediaWiki, reStructuredText, and Textile. Although supporting multiple formats is beyond the scope of this series, we’ll apply the chain-of-responsibility design pattern to keep open the possibility of doing so. The following figure shows a high-level diagram for typical Markdown editors: A more general form is shown in the following figure: Notice how the high-level components are similar to the previous diagram: a text editor, a text processor, and an output format. For our proposed editor, the text editor contains a text string that is fed through some machinery that produces another string. By leveraging the aforementioned design pattern, our text editor can edit more than merely Markdown. We’ll see how using design patterns results in software that is easily extended to add more source document formats. Consider the following diagram: In the first processor chain, a Markdown document is converted directly to HTML. That will be our focus. In the second processor chain, a MediaWiki document references variables that are first interpolated to produce a document containing the resolved interpolated values, which is subsequently converted to HTML. As we’ll see, the effort to develop a general solution is on par with a Markdown-specific solution. You may wish to download the finished files, found at the end of the article. Processors The application is fueled by text processing, so we’ll develop the core software components first, as follows: Processor — Defines how objects chain together to transform documents. ExecutorProcessor — Provides a base processor chain implementation. ProcessorFactory — Creates processor chains capable of transforming a given document type into its final format. MarkdownProcessor — Transforms Markdown documents into HTML. Let’s build these out. Create Package Begin where we left off by creating a new package as follows: Start IntelliJ IDEA. Open the mdtexfx project. Expand mdtexfx → src → main → java → com.mdtexfx in the Project panel. Right-click com.mdtexfx. Click New → Package. Set the value to: com.mdtexfx.processors. Press Enter to create the package. The package, where we’ll organize our processor-related classes, is created. Processor Create a new interface in the processors package as follows: Right-click processors in the Project panel. Click New → Java Class. Set Name to: Processor Double-click Interface to accept its creation. Change the interface definition to the following: package com.mdtexfx.processors; import java.util.Optional; import java.util.function.UnaryOperator; public interface Processor extends UnaryOperator { default Optional> next() { return Optional.empty(); } } The key points are that: the generic type T will be a String in our editor implementation; the UnaryOperator communicates that we want to perform some operation—a string transformation—on an input value to compute some output value; and returning the sentinel value of Optional.empty() denotes that, by default, each processor is a terminal link in the chain. Using an empty sentinel avoids introducing a null reference. ExecutorProcessor The application’s engine is the ExecutorProcessor class. Create it in the processors package having the following naïve definition: package com.mdtexfx.processors; import java.util.Optional; public class ExecutorProcessor implements Processor { private final Processor mNext; public ExecutorProcessor( final Processor successor ) { mNext = successor; } @Override public T apply( final T data ) { Optional> handler = next(); T result = data; while( handler.isPresent() ) { final Processor processor = handler.get(); result = processor.apply( result ); handler = processor.next(); } return result; } @Override public Optional> next() { return Optional.ofNullable( mNext ); } } Although this works, the loop that transforms the data does not significantly improve upon its equivalent pre-Java 8 implementation: while( handler != null ) { result = handler.apply( result ); handler = handler.next(); } Replacing the null sentinel with an Optional one was a good first step. We can go further by eliminating the call to handler.get(), which will be deprecated in the future. (To understand the reason for its deprecation, read the mailing list post by the method’s author, Brian Goetz; for alternatives, see JDK-8140281.) Change the method and add a new inner class as follows: @Override public T apply( final T data ) { final var result = new MutableReference<>( data ); Optional> handler = next(); while( handler.isPresent() ) { handler = handler.flatMap( p -> { result.set( p.apply( result.get() ) ); return p.next(); } ); } return result.get(); } private final class MutableReference { private T mObject; MutableReference( final T object ) { set( object ); } void set( final T object ) { mObject = object; } T get() { return mObject; } } Note these effective changes: using a final MutableReference instance satisfies the lambda expression, p -> { ... }, which requires final or effectively final variables; calling handler.get() is now handled implicitly whereby the lambda expression receives the target Processor instance using the variable p; and invoking flatMap executes the lamba expression, which transforms the input using the current processor then returns the next Processor link in the chain. Implementing the loop using the Stream class is another possibility, but be sure to measure the performance. This particular chain-of-responsibility pattern doesn’t lend itself to parallelization, so any such anticipated performance gains are nebulous as best. Similarly, using an AtomicReference class would be less code at the expense of poorer performance and possible misunderstanding over use of a concurrent class. To prove this, we tested a few different scenarios, which we named as follows: Mutable reference — The baseline apply method presented above. Atomic reference — Replaces the MutableReference with an AtomicReference. Optional get() — Swaps the flatMap call for handler.get(). Recursion — Applies the processors’ transformations recursively. Stream iterator — Invokes Stream.iterate with a flatMap and reduce call. The following table lists the highest scoring trial run (of three runs) for all implementations, against different numbers of processors in the chain: Benchmark Name Processors Operations per μs Mutable reference 2 49.447 Mutable reference 4 24.493 Mutable reference 8 8.981 Mutable reference 16 6.018 Mutable reference 32 2.686 Mutable reference 64 1.539 Atomic reference 2 24.409 Atomic reference 4 14.065 Atomic reference 8 7.527 Atomic reference 16 3.752 Atomic reference 32 2.112 Atomic reference 64 1.054 Optional get() 2 37.256 Optional get() 4 21.24 Optional get() 8 10.851 Optional get() 16 6.299 Optional get() 32 2.645 Optional get() 64 1.511 Recursion 2 27.009 Recursion 4 15.298 Recursion 8 7.469 Recursion 16 3.332 Recursion 32 2.251 Recursion 64 0.952 Stream iterator 2 6.672 Stream iterator 4 7.964 Stream iterator 8 4.405 Stream iterator 16 1.817 Stream iterator 32 1.138 Stream iterator 64 0.701 We tested the performance using the Java Microbenchmark Harness. All runs produced the same overall result: streams perform poorly, having the slowest number of operations per microsecond; and using an object wrapper yields an optimal solution for our generic processor chain. ProcessorFactory Different source documents have different processing chains. The responsibility of mapping document types to specific chains falls to the ProcessorFactory class. In the same processors package, create a new class using the following content: package com.mdtexfx.processors; public class ProcessorFactory { public static Processor create( final ProcessorContext context ) { final var successor = new HtmlProcessor( context ); final var processor = switch( context.getMediaType() ) { case UNDEFINED -> new IdentityProcessor( successor, context ); case TEXT_MARKDOWN -> new MarkdownProcessor( successor, context ); }; return new ExecutorProcessor<>( processor ); } } There’s a lot going on here and work to do before the code will compile. Broadly, the ProcessorFactory class introduces a few new concepts, including: ProcessorContext — Guards against parameter explosion when creating Processor instances. Different processors have distinct requirements; when requirements change for individual processors, we want to avoid changing ProcessorFactory, in accordance with the single-responsibility principle. IdentityProcessor — Avoids creating a special case when the input format is unknown. The processor’s name borrows from the identity function because its transformation returns the input document without modification. See the download section for the implementation. MediaType — Uses codes defined by the Internet Assigned Numbers Authority (IANA) that describe file formats and format contents. We know we’ll need to associate files with processor chains; leveraging standard definitions permits decoupling knowledge about file name extensions from the ProcessorFactory. Further, it enables writing ProcessorFactory unit tests without having to provide java.io.File instances. This leaves us with implementing the MarkdownProcessor and HtmlProcessor. Gradle Before we can implement the MarkdownProcessor, we need to instruct the compiler where it can find various libraries. Update build.gradle by changing the dependencies section to reference Vladimir Schneider’s efficient and highly configurable flexmark-java library; since we know we’re going to need the WebView class, update the javafx section to include the javafx.web module. Together, both sections should resemble the following: javafx { version = '15' modules = ['javafx.controls', 'javafx.web'] configuration = 'compileOnly' } dependencies { def v_commons_io = '2.8.0' def v_flexmark = '0.62.2' def v_junit = '5.6.2' runtimeOnly "org.openjfx:javafx-controls:${javafx.version}:linux" implementation "commons-io:commons-io:${v_commons_io}" implementation "com.vladsch.flexmark:flexmark:${v_flexmark}" testImplementation "org.junit.jupiter:junit-jupiter-api:${v_junit}" testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${v_junit}" } The above snippet applies the DRY principle in that the version numbers for third-party libraries are defined but once. MarkdownProcessor If the ExecutorProcessor is the application’s engine then the MarkdownProcessor is a wheel. With the infrastructure complete, implementing a processor that converts Markdown documents to HTML is straightforward. We need to include a Markdown parser and then implement the MarkdownProcessor class. Create the MarkdownProcessor class using the following code: package com.mdtexfx.processors; import com.vladsch.flexmark.html.HtmlRenderer; import com.vladsch.flexmark.parser.Parser; import com.vladsch.flexmark.util.ast.*; public class MarkdownProcessor extends ExecutorProcessor { private final IParse mParser = Parser.builder().build(); private final IRender mRenderer = HtmlRenderer.builder().build(); public MarkdownProcessor( final Processor successor, final ProcessorContext context ) { super( successor ); } @Override public String apply( final String markdown ) { return mRenderer.render( mParser.parse( markdown ) ); } } The work we did up-front has paid off with the simplicity so afforded. Developing an AsciiDoc processor or introducing our own custom processors now has an easy-to-follow template. Integrating flexmark-java introduces the following concepts: IParse — Parses a string containing Markdown into an abstract syntax tree (AST) represented by a node. IRender — Renders a node into an HTML document, represented by a string. We may be concerned about the performance impact of transforming a Markdown document into an AST—a type of document object model—only to rebuild the HTML from a string into its own document object model. As we’ll see, this is not a bottleneck. Next, we’ll implement the processors’ context and HTML processor, then wire everything up in the main application. ProcessorContext Recall how the factory design pattern helped us create a processor capable of transforming a media type (e.g., MediaType.TEXT_MARKDOWN) into the desired output format. Another common creational design pattern is the builder design pattern. Builder classes are useful when we want to create a new instance of a class that has many possible configurations. We’ll forgo the builder pattern for the ProcessorContext class because, at present, it takes only two parameters, implemented as follows: public class ProcessorContext { private final MediaType mMediaType; private final HtmlRenderer mHtmlRenderer; public ProcessorContext( final MediaType mediaType, final HtmlRenderer htmlRenderer ) { assert mediaType != null; assert htmlRenderer != null; mMediaType = mediaType; mHtmlRenderer = htmlRenderer; } MediaType getMediaType() { return mMediaType; } HtmlRenderer getHtmlRenderer() { return mHtmlRenderer; } } Accessor methods can be considered poor design because they violate information hiding by exposing internal data to calling classes. This means that if a class changes its accessor method signatures, then its calling classes will likely have to change as well, which is contrary to the single-responsibility principle. As a compromise, the accessor methods have been declared package protected to restrict their use to only classes within the same package, limiting the scope of ripple effects that may be caused when refactoring the class. When data classes and sealed types become available, the ProcessorContext class would be suitable candidate for rewriting as a record, which is a restricted form of a class. MediaType For the ProcessorContext class to compile, it needs MediaType to be defined. We could import a library that defines a Markdown media type constant, but for the two constants we need, importing an entire library is excessive. Instead, add the following enumeration to a new package named com.mdtexfx.io: public enum MediaType { UNDEFINED( "" ), TEXT_MARKDOWN( "text/markdown" ); private final String mMediaType; MediaType( final String mediaType ) { mMediaType = mediaType; } public String toString() { return mMediaType; } } The reason for placing it in the .io (input/output) package is because in the future we’ll want to associate file name extensions with media types. HtmlProcessor Like the MarkdownProcessor before it, the processor infrastructure investment makes writing the HtmlProcessor class a quick endeavour: package com.mdtexfx.processors; import com.mdtexfx.html.HtmlRenderer; public class HtmlProcessor extends ExecutorProcessor { private final HtmlRenderer mHtmlRenderer; public HtmlProcessor( final ProcessorContext context ) { mHtmlRenderer = context.getHtmlRenderer(); } @Override public String apply( final String html ) { return mHtmlRenderer.render( html ); } } Here we’ve opted to use an as yet undefined HtmlRenderer interface that exposes a String render( String ) method signature. This approach allows the implementation details regarding how the HTML document is exported to be controlled by the class that calls into the ProcessorFactory, rather than the ProcessorFactory itself. One disadvantage is that it means an additional class and interface. A big advantage is that our unit tests won’t need to provide a graphical user interface to verify the HTML output. HtmlRenderer & HtmlPreview For the HtmlProcessor to function, it needs an implementation-independent mechanism to display HTML. By using an interface, we abide by the guideline of “program to an interface, not an implementation,” a design principle set out and described at length by Erich Gamma, one of the Design Patterns authors. The HtmlRenderer class defines a single method that hardly deserves mention: package com.mdtexfx.html; import javafx.scene.Parent; import javafx.scene.web.WebView; public interface HtmlRenderer { String render( final String html ); } The HtmlPreview class is where we finally cut some code of interest: package com.mdtexfx.html; import javafx.scene.Parent; import javafx.scene.web.WebView; public class HtmlPreview extends Parent implements HtmlRenderer { private final WebView mView; private static final String HTML_PREFIX = "" " < !DOCTYPE html > < html lang = 'en' > < head > < title > < /title> < /head> < body > "" "; private static final String HTML_SUFFIX = "