r/java • • Oct 08 '20

[PSA]/r/java is not for programming help, learning questions, or installing Java questions

324 Upvotes

/r/java is not for programming help or learning Java

  • Programming related questions do not belong here. They belong in /r/javahelp.

  • Learning related questions belong in /r/learnjava

Such posts will be removed.

To the community willing to help:

Instead of immediately jumping in and helping, please direct the poster to the appropriate subreddit and report the post.


r/java • • 8h ago

Lessons learned while optimizing my Java parser combinator library

31 Upvotes

A while ago, I shared my Java parser combinator library, jjparse, here on reddit. Since then, I've been working on a few improvements, which eventually led to the release of a new major version. I thought this would be a good opportunity to share some of the things I've learned along the way, give a little update on the project, and hopefully get some feedback and discussion going.

As you might know, parser combinators typically rely heavily on backtracking (which is the default in jjparse). Consequently, parser failures are not necessarily exceptional but often part of perfectly successful parsing operations.

My original implementation was pretty straightforward: Whenever a parser failed, it generated diagnostic information explaining what went wrong (i.e., an error message). A perfectly reasonable approach from a conceptual point of view, but as it turned out, not particularly efficient.

Even when parsing eventually succeeded, all those intermediate failures generated error messages that were immediately discarded. And with enough backtracking, that's a lot of unnecessary heap allocations. I only noticed this when I started experimenting with larger inputs for benchmarking. Smaller examples worked just fine, but larger ones showed wildly varying runtimes. Profiling eventually revealed that a substantial part of the runtime was spent dealing with all the garbage produced by error reporting.

So, my lesson learned here is to keep allocations on parser failures to a minimum and only generate detailed error messages when they're actually needed. Looking back, it seems pretty obvious, but I guess that's often the case with these things.

The new implementation stores the information needed for diagnostics in the parser itself. Failures now only point to this information, and actual error messages are generated lazily when requested. I've also added tracking of the farthest failure to provide more useful diagnostics in the presence of backtracking. Redesigning the error reporting also made me reconsider what information a useful error message should actually contain.

For ordinary syntax errors, expectations work quite well:

unexpected token 'x', expected token 'y'

However, parser combinators can also recognize languages beyond the class of context-free languages, and not every failure is purely syntactic in the typical sense. For example, when parsing identifiers that must not be reserved keywords, an explanation might be more useful than an expectation:

unexpected token 'x', because 'x' is a reserved keyword

This led me to distinguish between an expectation and a reason, both of which are optional. The general format of a reported error is now:

unexpected <input token>, expected <expectation> because <reason>

I'm still not entirely sure how flexible this design will turn out to be, particularly when it comes to composing expectations and reasons across parser alternatives. Should reasons be combined? Should expectations take precedence? What if different alternatives fail for entirely different reasons? I'd be interested in hearing what you think!

That aside, all these changes required some breaking API changes, hence version 2.0. A couple of other things I've been working on in this regard:

  • Input filtering: Previously, whitespace skipping was somewhat of a hack built into the parser implementation. Now, inputs can be filtered using another parser (input.withFilter(parser)). Whether the implementation is particularly efficient remains to be seen, but I quite like how the API turned out.

  • Benchmarks: I've finally added a JMH benchmark to the repository, parsing 10 - 100 MB of synthetically generated, non-repetitive JSON into an AST. The result is around 30 - 34 MB/s. Nothing to write home about compared to specialized parsers, but probably at least roughly comparable to other parser combinator libraries on the JVM (although I haven't tested that yet, tbh). One obvious performance bottleneck is the generic input abstraction, which means that even parsing plain text involves boxing and unboxing characters (or rather Unicode codepoints). I might eventually optimize this under the hood in the future, but I'd really like to preserve the generic API. After all, that's kind of the whole point of the library.

Anyway, I'm still figuring things out, and I'm sure there are plenty of things that could be improved. I'd particularly love to hear your thoughts on the API and overall design. Criticism, suggestions, alternative approaches, or even a PR are all very welcome!

For anyone interested in taking a look: https://github.com/BjoernLoetters/Jar-Jar-Parse


r/java • • 2d ago

SMILE 7.2.0 on Java 25: Replacing JNI with Project Panama FFM (OpenBLAS/LAPACK/ONNX), Compact Object Headers, and in-process TreeSHAP

50 Upvotes

I've been maintaining SMILE (Statistical Machine Intelligence and Learning Engine) for a little over ten years now. Over the past few months I rewrote the native compute layer to target Java 25 LTS directly, and wanted to share a few notes on how java.lang.foreign and Compact Object Headers held up in practice.

Most Java backend teams I talk to (running Kafka, Flink, Quarkus, or Spring Boot) still end up running a Python sidecar over HTTP or gRPC just to score a gradient boosted tree, run an .onnx model, or compute SHAP explanations. Paying 10-40 ms of network + JSON/Protobuf overhead for a tree traversal that takes 80 microseconds in memory always bugged me.

In 7.2.0, I finally ripped out the old JNI/JavaCPP bridges across smile-linalg, smile-deep, and smile-onnx and replaced them with jextract-generated MethodHandle downcalls over MemorySegment and Arena.ofConfined().

Moving off JNI fixed our biggest production headache: GC pinning stalls from GetPrimitiveArrayCritical during concurrent BLAS/LAPACK calls. Matrix decompositions (LU, QR, Cholesky, SVD, eigendecomposition) and manifold learning (UMAP, t-SNE) now pass off-heap MemorySegment buffers straight to OpenBLAS, LAPACK, and ARPACK, and deterministic try (var arena = Arena.ofConfined()) blocks free native tensors immediately instead of waiting on Cleaner finalizers. We used the same FFM approach to bind the ONNX Runtime C API (onnxruntime_c_api.h) and llama.cpp so .onnx and GGUF models run on the calling JVM thread.

The second big win on Java 25 was -XX:+UseCompactObjectHeaders (JEP 519). Dense matrices live in primitive arrays or off-heap slabs, but random forests, kd-trees, cover-trees, and DataFrame metadata still allocate millions of small objects. Shrinking HotSpot object headers from 16 bytes to 8 bytes dropped heap usage on our large tree ensembles by about 15-22% and noticeably helped L1/L2 cache hit rates during tree traversal under Generational ZGC.

Finally, since regulated teams kept telling me they only kept Python around for shap.TreeExplainer, I implemented exact O(T * L * D2) TreeSHAP for RandomForest and GradientTreeBoost (plus parallel KernelSHAP) directly in Java, and locked down .sml model loading with SmileObjectInputStream (ObjectInputFilter allow-lists). On our 18-model benchmark matrix, 300-tree x 64-feature GradientTreeBoost and RandomForest models score in 0.43-0.54 ms on the JVM (1.14-1.22 ms native Linux p99), and bounding maxDepth = 6, maxNodes = 128 cuts single-row TreeSHAP latency 95x from 274 ms down to 2.88 ms p99 (0.91 ms on GBM):

import smile.classification.RandomForest;
import smile.data.DataFrame;
import smile.data.Tuple;
import smile.data.formula.Formula;
import smile.io.Read;

DataFrame df = Read.parquet(transactions.parquet);
RandomForest model = RandomForest.fit(Formula.lhs(is_fraud), df);

Tuple row = df.get(0);
int label = model.predict(row);
double[] shap = model.shap(row); // exact in-process TreeSHAP in <1-3 ms

For folks who want a standalone sidecar instead of embedding the JAR, there is also a Quarkus-based smile-serve container (ghcr.io/haifengl/smile-serve:latest-micro, around 200 MB, 0.4s startup, ~80-140 MiB idle RAM) that serves .sml and .onnx models over HTTP or Unix domain sockets with ?explain=true.

GitHub repo is at https://github.com/haifengl/smile (core libraries are GPLv3 + commercial OEM; smile-serve is AGPLv3 + commercial runtime; Studio is BSL 1.1 - details at https://aihalo.dev/licensing.html).

If anyone else here is migrating JNI heavy numerical code to jextract on Java 25 or testing JEP 519 on pointer-heavy data structures, I'd be happy to compare notes.


r/java • • 2d ago Bravo

Rainbow Gum logging released v1.0.0 and why you should switch Now!

37 Upvotes

After 3 years and an embarrassing amount of personal and business time spent I finally am releasing Rainbow Gum 1.0.0.

I spent an enormous amount of time on Rainbow Gum because I know how important getting logging right is despite on the surface appearing very easy.

While I may be critical of the existing logging frameworks Rainbow Gum stands on the shoulders of these giants and why it is largely better is because of them.

Rainbow Gum is:

  • Is very secure. I can't say its more secure than the others because that would get me in trouble but if you were to put a gun to my head and ask me pick between Rainbow Gum, Logback, or Log4J2 not to have a security issue I would pick my library.

  • Faster than Log4J2 - Log4J2's claims of speed are very dated when zero GC options were vogue. Log4J2 is actually often slower than logback.

  • Smaller than Tinylog - Tinylog may have a small code footprint but it actually requires several dependencies like java.sql.

  • Loads of modern features - Scoped Values, colored themes, JSONL, LTSV built into core etc.

There really only 4-5 logging libraries (implementations) in the Java ecosystem so if your knee jerk reaction is not another one just know that there are way more JSON serializers, ORMs, web frameworks and build systems. All of these require tremendous effort to switch or even try.

With a couple changes of your POM you can switch to Rainbow Gum.

Please try it!

Rainbow Gum was coded by me entirely up to 0.10.0 I think. It is now heavily code assisted. I had to to make sure it was as secure as possible but have reviewed every change. I am exhausted! I have made it transparent by letting the agents coauthor the commits.

https://github.com/jstachio/rainbowgum

And if you don't think Rainbow Gum is as good as what I say it is you can always have several agents do a blind test on all the logging frameworks. Just tell them to remove authors and rename etc. You can and should always benchmark as well.

Special thanks to the JDK team and JSpecify team.

I will add more links later but if I add too many I will be automod.


r/java • • 2d ago

Announcing Oracle WebLogic 26 (Jakarta EE 11 compatible)

Thumbnail
blogs.oracle.com
29 Upvotes

r/java • • 1d ago

If you don't have a (Java) security process, you are negligent...

0 Upvotes

In the last weeks: two critical PHP security updates, eMuhdra certificate issues in all communication layers, 53 CVEs issues fixed in the Java October update,... Security is a hot topic, now more than ever!

How safe is your Java application, and how safe are the dependencies it ships with? OWASP's Jim Manico has a blunt answer for teams without a security process: you are negligent. Then he explains how to fix that. A very entertaining and informative talk in Friends Of OpenJDK (foojay) podcast 105!

Available on YouTube and in your favorite podcast app:
https://www.youtube.com/watch?v=Q9EIBoek2zc


r/java • • 3d ago

Borrowed Engines: Detroit, Valhalla, GraalVM Next and the Islands Next to Java

Thumbnail
jvm-weekly.com
36 Upvotes

r/java • • 3d ago

Clique 4.1.0: Zero deps CLI styling library for Java

18 Upvotes

So what's Clique?

If you missed my older posts, Clique is a zero-dependency CLI styling library for Java that is no-color.org compliant.

What's new in 4.1.0:

This is mostly a stability/correctness release. Since 4.0.3 I spent my time going through the library looking for issues and edge cases and fixed a good amount of em. So, these are the ones you'd might likely notice:

  • Emoji and CJK text now render at the right width in Divider, tables and frames. Gradients also no longer corrupts emoji.

  • NO_COLOR / disableCliqueColors() is now respected everywhere. Reset codes were leaking in some components.

  • OSC 8 hyperlinks no longer count their URL toward width, so tables and frames with links line up.

  • Frame no longer re-renders nested components repeatedly

  • Tree and ItemList detect cycles properly (previously a.add(b); b.add(a) could cause a StackOverflowError).

  • Tables were rewritten internally. The public API is unchanged, but column widths are now computed at render time, so replacing a long cell with a short one shrinks the column correctly.

If you discover any bugs/issues while using the library, just lmk by opening an issue or a pull request.

Links:


r/java • • 3d ago

Keystore: CLI-Based key manager for Java

3 Upvotes

Some weeks ago I started working on a Java CLI-based keystore app. I know some mature ones already exist on the market, but I wanted it to be as simple to use as possible and include commands that suit my work.

GitHub Repository: https://github.com/mateasmario/keystore

Until this moment, some functionality includes:

  • adding, removing, getting and listing custom keys

  • copying a key without displaying it on the screen

  • listing all keys without displaying their value on the screen

  • generating keys based on certain criteria (length, include digits, uppercase, lowercase, symbols, etc.)

  • generating OTP values based on the current time (elapsed seconds after epoch time - 1970) and already stored keys

Managing keys requires a password that is set by the user (keyboard input) when the first key is added/generated to the keystore. That password is used to AES-Encrypt the keystore file in the user data directory (e.g. %localappdata% for Windows).

There is also the possibility of switching to the FILE mode, which is going to ask the user for a file path instead of typing a password. That file contains the key that can be used to decrypt (maybe you keep your secrets on a USB drive). You don't need to create the key manually. Even in FILE mode, the user's still going to be asked for a password the first time, which will be used to derive a key and store it in the specified file path.

Was curious on how I can improve it: how to improve both user experience and also add features that might be helpful to you, the developer community.


r/java • • 4d ago

Java 25 (with virtual threads) for a startup

Thumbnail reddit.com
22 Upvotes

r/java • • 3d ago

GitHub - maksoud13/tenantwall: Automatic tenant filter injection for multi-tenant applications. Zero code changes.

Thumbnail
github.com
2 Upvotes

Hey everyone,

‎I’ve been working on a side project to solve a problem I keep running into: silent multi-tenant data leaks. Relying on developers to remember   WHERE tenant_id = ?   in every query is a ticking time bomb.

‎Using database-level Row-Level Security ⁦(RLS)⁩ is safer, but it completely restricts you to one database vendor, which isn't always feasible.

‎So, I tried building a Java agent that works at the JDBC layer. It intercepts the SQL right before execution and injects the tenant filter, meaning it works with any database and requires zero code changes in the app.

‎I’m in the very early phases. I have integration tests passing for MySQL and SQL Server, but I know real-world SQL is messy.

‎If you have a few minutes, I’d really appreciate your opinion on the approach, the architecture, or any red flags you spot in the code.

‎Repo: https://github.com/maksoud13/tenantwall


r/java • • 5d ago

JUring - async file I/O for Java (Part 2)

58 Upvotes

Hi everyone!

I have been working on and off on IO_Uring bindings for Java. It's been two years already since the last update. It has changed a lot since then, and it became a lot quicker as well! Dropped Liburing for directly mapping the rings into memory and no more allocations on the hot path.

It still has two public API's one mimics Liburing in a way and handles everything for you, the other is the nice one that support Virtual threads and unmounts them on file I/O.

You can find the project here if you are interested :-) https://github.com/davidtos/JUring

I wanted to share it, because there doesn't seem to be a whole lot of FFM code out there (on the performance side). I Think the most interesting part besides the actual ring mangement, are the Bitpacker and "Global memory view". There are JMH and jcstress in there as well, which were a separate challenge to write.

It is still a work in progress, and I'm still learning new things every day. Would love to hear your thoughts, feedback, or critiques if you've worked on or used something similar. Happy to answer any questions about the implementation!


r/java • • 5d ago

GlassFish 9.0.0-M4 (Jakarta EE 12) released

Thumbnail github.com
18 Upvotes

r/java • • 5d ago

Five Java stacks on a 512 MiB VPS: memory, startup, and portable business logic

24 Upvotes

I've been experimenting with resource-efficient Java applications, particularly how different frameworks behave on small VPS instances with limited memory.

While exploring lightweight alternatives to Spring Boot, I became interested in another question: how much of an application can remain unchanged when switching HTTP frameworks?

Rather than rewriting repositories, services, and transaction handling for every experiment, I tried keeping the business logic independent of the HTTP framework.

I ended up comparing five stacks:

  • Spring Boot + Spring JDBC

  • Javalin + CoilBase

  • Avaje Jex + CoilBase

  • Micronaut Data JDBC

  • Micronaut + CoilBase

Spring Boot and Micronaut Data served as independent reference implementations. The other three shared the same services and repositories through CoilBase, a small Jdbi-based library I'm developing.

I ran all five on a 1-vCPU, 512 MiB Linux VM with colocated MariaDB, using both HotSpot and GraalVM Native Image.

Some observations:

  • HotSpot: Avaje Jex + CoilBase used ~116 MiB application RSS, Javalin + CoilBase ~121-136 MiB, and Spring Boot ~181-188 MiB.

  • Native Image: Avaje Jex + CoilBase used ~33-51 MiB, Javalin + CoilBase ~58-59 MiB, and Spring Boot ~84-85 MiB.

  • All five completed the same 1,500-request workload without errors, with independent database correctness checks.

  • Native Image substantially reduced memory usage and startup time, but compilation and compatibility work introduced additional costs.

The workload was intentionally light (5 requests/sec), with two accepted runs per configuration. This is primarily a comparison of runtime footprint and application structure, not a throughput or saturation benchmark.

What I found particularly interesting is that Javalin, Avaje Jex, and Micronaut have quite different approaches to routing, dependency injection, and application lifecycle, yet the same business logic worked across all three. Only the framework-specific integration and application wiring needed to change.

I documented the experiment, including the methodology, measurements, and limitations:

Article:
https://pvrlabs.xyz/articles/five-java-stacks-512mb-vps.html

Public GitHub experiment (methodology and results):
https://github.com/PVRLabs/experiments/tree/main/java/coilbase-framework-comparison

I'd be interested in feedback on the methodology and whether the observed memory differences match your experience with these frameworks on constrained deployments.


r/java • • 5d ago

Withdrawing the Primitive Patterns preview feature in JDK 28

Thumbnail mail.openjdk.org
90 Upvotes

Primitive Types in Patterns will be removed in JDK 28 and will no longer be available in that release.

The Valhalla project has been exploring a new vision for Java numerics, including the possibility of user-defined numeric types. New kinds of numeric types would need to support exact numeric conversion, and vice versa. Given that the Valhalla work is still evolving, it would be premature to finalize Primitive Types in Patterns with their current definition of exact numeric conversion.


r/java • • 6d ago

Beyond mvn install: Maven Practices That Pay Off

Thumbnail
foojay.io
128 Upvotes

r/java • • 6d ago

JairoSVG Release v1.0.16

Thumbnail github.com
13 Upvotes

This version has a fix for music notation rendering, along with bumps on dependencies.


r/java • • 6d ago

A Valhalla Ticker in One int

Thumbnail
inside.java
39 Upvotes

A ticker symbol is an abbreviation that uniquely identifies a financial product such as a publicly traded share. Well-known ticker symbols are "ORCL" and "GOOGL". In this article, we will devise a Valhalla value class that can represent a ticker symbol using only a single int field.


r/java • • 7d ago

EclipseLink 6.0.0-M1 released!

Thumbnail
github.com
25 Upvotes

r/java • • 7d ago

fose v0.2.1: reworking the wait queue and waiting scheme in my flat combiner

0 Upvotes

Hi, a while back I posted about fose, a flat combining primitive for Java. I've just recently released v0.2.1. My main focus for this release was to experiment with how threads enqueue onto the wait queue, wait on their nodes and reduce synchronization between the combiner and waiters. Before I get into the changes, let me quickly explain what flat combining is for anyone new to it.

Flat combining is a technique for making concurrent data structures scale under contention. Instead of every thread fighting over a lock and bouncing the structure's cache lines between cores, each thread publishes its operation as a request. One thread becomes the combiner, takes the lock and applies the pending requests on behalf of the others, then hands each thread its result. The structure stays hot in a single core's cache while everyone else just waits for their answer.

So, in my previous enqueue scheme, threads CAS'd on the tail and then linked their predecessor's next pointer to their own node. To find out whether their request had been combined, threads spun on their predecessor's next pointer, and to find out whether they could contend to become the combiner, they spun on the head field, which the combiner wrote to after each pass. On the combiner side, notifying a thread meant writing the result into that thread's node and then nulling out its predecessor's next pointer.

The problem was the memory traffic this created between the combiner, the waiter and the waiter's predecessor, which hurt the structure's performance.

The new scheme is quite similar to how CC-Synch enqueues nodes. A thread swaps its pre-allocated node into the tail with an atomic exchange, claims the previous tail node, writes its operation there and waits on it. The main benefit here is that each waiter only ever touches a single node once enqueued, and the combiner only has to invalidate one waiter's cache line instead of two to signal that its result has been applied.

This enqueue scheme also opened the door to some other optimizations. Previously, a spinning waiter had to read two atomic variables (next and head) to tell whether its request had been applied. Now each waiter only reads one atomic variable (signal) in the node it claimed and tests it for its result. This also let me make head non-atomic, which reduces synchronization between the combiner and waiters even more.

These changes didn't fit well with the spinner cap from v0.1.0, where a waiter only spun if its sequence number was within NCPU * 0.6 of the last served op. Keeping it would have meant adding extra synchronization between combiners and waiters, which is exactly what I was trying to reduce. The cap was useful though. It bounded the combiner's tail latency when oversubscribed, since tail latency grew with the number of active spinners, and it gave me a knob for trading tail latency for average latency.

I also tried a few replacements: a single atomic spinner count (with weaker ordering), a shared adaptive spin budget updated based on how often threads were signalled while spinning, and parking immediately. Only the immediate park strategy bounded tail latency when oversubscribed, but it made average latency 75% worse, mainly because syscalls are expensive.

So I settled on something simpler. Now every waiter spins briefly (32 spins) and then parks. When a parked thread wakes up, it rechecks whether it's next in line to combine. If it is but fails to acquire the lock, another combiner is mid-pass and will probably serve it soon, so instead of parking again it gets a new but much smaller spin budget, similar to what AQS does. I dropped the spin and post-spin budgets from 1024/128 to 32/8 since every thread spins before parking now, and I didn't want tail latency to get too bad when oversubscribed.

Funny enough, I expected this tight budget to hurt average latency badly without helping tail latency much, but it was quite the opposite and I was pleased with the results. I could raise the budget a bit, but 32/8 looks like a great spot from what I measured. Raising it doesn't noticeably improve mean latency in the common case, and it hurts tail latency a lot when oversubscribed.

One optimization I considered is non-fair handoff, which I haven't implemented but plan to try. It's different from barging though. With non-fair handoff, after a combine pass, if the next waiter is parked, the combiner doesn't unpark it right away. It walks the queue for an unparked waiter and marks that one to contend to be the combiner, and the nodes it skipped get marked before unparking so those threads can detach and re-enqueue. The idea is to avoid stalling everyone else while the handed-off thread waits to be rescheduled by the OS. It's a bit more work to implement though, and I haven't measured whether this is actually an issue, so I'm not building it until I'm sure.

Benchmarks

These come from my same ol' machine as before (Intel i5, 4 cores / 8 CPUs running on Windows 11).

  • Cache line benchmark: Mean latency improved across 2 to 8 threads in v0.2.1. At 8 threads, the combiner went from 968 to 839 ns, and the speedup over a non-fair ReentrantLock went from 1.96x to 2.33x.

  • Heap benchmark: The numbers improved at 4 to 8 threads (1,437 to 1,227 ns at 6 threads and 2,508 to 1,950 ns at 8). However, v0.2.1 did get worse than v0.1.0 when oversubscribed, from 4,569 to 5,858 ns, so the speedup over a non-fair ReentrantLock dropped from 1.73x to 1.43x.

Everything here comes from one machine, so if you can run the benchmarks on Linux or a machine with more than 4 cores, I'd love it if you shared the results on the repo's issues page, with your machine specs included.

<dependency>
    <groupId>io.github.kusoroadeolu</groupId>
    <artifactId>fose-core</artifactId>
    <version>0.2.1</version>
</dependency>

r/java • • 9d ago

How hard is it to check for whitespace?

Thumbnail
serce.me
56 Upvotes

r/java • • 9d ago

Ask the Architects at Devoxx 2026

Thumbnail
youtube.com
47 Upvotes

r/java • • 10d ago

What's New in IntelliJ IDEA 2026.3

Thumbnail
blog.jetbrains.com
80 Upvotes

r/java • • 10d ago

Project Detroit: From Foreign Libraries to Foreign Language Runtimes

Thumbnail
youtu.be
30 Upvotes