Ask HN: What is a modern Java environment?
Hello,
I used Java a very long time ago, like 1.6, and I'm wondering what using Java at work in ~web backend looks like today.
What elements of your stack would you consider most important? Do most projects lean heavily on Spring Boot or something else? Which IDE? Pointers to refreshers or concise introductions you might give a new hire would be handy too.
152 comments
[ 3.7 ms ] story [ 227 ms ] threadI haven’t been paid to work with Spring since 2013, every project I’ve worked on since has used Guice, maybe Dropwizard.
JAX-RS is the dominant paradigm for web back ends these days, particularly if you are writing single page applications.
The work stealing mechanism used by streams doesn't really work. Frequently I've seen people get something like a 1.7x speedup on an 8 core machine and was able to get a 7.8x speedup on the same machine using a ThreadPoolExecutor.
For common "embarrassingly parallel" problems there are two parameters you need to set: (1) How many threads to use, and (2) How fine to subdivide the problem.
Often the basic work unit takes much less time to complete than the time it takes to switch between threads. For instance a raytracer can probably trace one ray in less than the time it takes to communicate between threads. If you try to parallelize a task with too fine a granularity you get a slowdown not a speedup. You might find you get a good speedup over a fairly wide range of granularities (you might do well with anywhere between 100 and 10,000 rays) but batching of some kind is essential.
As for the thread count it depends on if the job is CPU bound. A CPU bound job needs about as many threads as you have cores or SMT "threads". If the job is I/O bound you usually need many more threads to maximize performance, but it's tricky. A web crawler might be able to support 100's or 1000's of threads but if you point all those threads at one server you might crash it, get banned, or both.
If the awkward streams API bought you good performance and reliability (let's see... just about zero support for error handling) that would be one thing but it doesn't.
Static typing working so well is not a special feature of the streams API but rather one of the rather brilliant engineering that went into JDK 8. You can easily write your own "map()" functions and other higher-order functions that do many of the things the Stream API does.
It would really be nice to see a better third-party API.
This is the opposite of my experience. I worked with Java a lot for the last 10 years or so. In the last 3 years literally each request I got for implementing stuff in Java also required Spring (Spring Boot) - and that was dozens of clients.
https://trends.google.com/trends/explore?date=today%205-y&q=...
Unfortunate, because it had (has?) a great Swing and UML editor, built-in for free.
Maybe I'm wrong and it's awesome now, but I haven't used it for a while in favor of Eclipse, and now, IntelliJ. This graph illustrates the IDE popularity between Intellij and Netbeans. IntelliJ became preferred over Netbeans about mid 2014.
https://trends.google.com/trends/explore?date=all&geo=US&q=i...
IMHO Netbeans was ahead of idea in many ways. But since they didn't buy commenteers to hype their product it's pretty much nowhere, at least I don't know anyone who uses it.
You can't stop IntelliJ from saving work. I like to kick of a build, and work on some new stuff while it's going on without saving until the build completes. That workflow is impossible in IntelliJ. You have to down tools, for a long build with lots of tests that can make IntelliJ slower over all. IntelliJ generally more responsive for most work, but that's because it's not using Java's type system to find errors as you work, errors are deferred, which often means they take longer over all to fix.
Much much better full text search. I can search through multiple projects. Not just root.
Square selection.
(I personally still use Eclipse)
When you can get it all in memory, you get the benefits of eclipse refactoring, when you can't you don't.
Ability to write code that is safe to refactor is one of the great things about Java, so it's a shame if you can't benefit from that.
Intellij does that a little bit of incremental compilation. They half integrated the eclipse Java compiler at some point but they never really made using it a thing. Most projects just hand off compilatio to Gradle or maven, which means you lose a lot of time waiting for that and dealing with various caches in intellij and gradle/maven getting out of sync with each other. The process for fixing that generally involves boiling the oceans and running the CPU at 400% for a few minutes.
Eclipse compiles two orders of magnitudes faster when you have it properly setup. I'm not exaggerating. It's really good at this. Intellij users have never seen this level of performance. I've tried to explain it a few times to users that insisted they had intellij configured just right. The minimum execution for a unit test after a 1 character code change is the key benchmark for this. Run a test, change something trivial, run it again. If that takes more than half a second and has a noticeable delay, it's not fast. Eclipse user to be closer to 50-100ms. Even on a (now) ten year old laptop. With intellij, it's closer to 3-5 seconds. They have improved things over the years but it never got even close to that level of performance.
I never needed to fiddle with "command line shortener" ridiculousness in Eclipse.
Better code navigation and discovery. Eclipse always finds everything, every called and callee to depth. Same with inheritance. Even where Idea has same views, for some reason they hide it and only former Eclipse users stumble on it.
Actually safe refactorings. Idea occasionally "refactors" by creating bugs.
I had some cases where Idea simply formats code wrong. And it was impossible to configure to formát it right.
Also, Eclipse compile by default. It is ridiculous to have to remember to press shortcut to compile in modern IDE. I know it is configurable, but still ridiculous default.
Square selection.
ETA: actually if you haven't used it since 1.6 you'd want to find an article on Java 8 as well that covers Streams.
I don't write too much code nowadays, but read a lot. From what I can see, here's the stack:
* intellij for an ide (with tons of plugins)
* prime MVC (https://github.com/prime-framework/prime-mvc) for the framework
* mybatis for SQL/queries
* java 17
I've also used dropwizard and spring. If it was a greenfield development with emphasis on developer productivity, I'd go with spring any day. Big dev community, tons of doco, a solution for any problem if you can find it.
* Try to use Kotlin where allowed (Maybe unpopular and bad faith response given that you asked about Java, but I don't care -- kotlin's Java interop is way more seamless than Scala or Clojure to the point that its often not even noticable) https://kotlinlang.org/
* IntelliJ
* Spring is pretty popular, I've seen a lot of people using Vertx
* Square libraries like Okio, okhttp, retrofit, wire https://github.com/square
* If you're not using spring, use Dagger for DI https://dagger.dev/dev-guide/
* Overlook netflix abandonware (another unpopular opinion, I'm sure)
* Use gradle, not maven. Use kotlinscript as the config language, not groovy
If you want a slow build and code that is fun to write but hell to read, that is good advice.
> Use kotlinscript as the config language
Extremely slow, that is.
I agree that these will slow down your build, but not significantly, and at the benefit of developer comfort to make changes. Incremental compilation largely erases these slowdowns.
Build scripts are generally written once and executed hundreds or thousands of times. It makes far more sense to optimize their performance rather than ease of writing.
Java, for me, personally, in my opinion, is on thin ice. I personally, in my opinion, would never work at a "Java shop" because in my opinion, personally, it conveys a disregard for technical suitability and individuality and supports the idea that engineers should be expendable. I believe, in my opinion, personally, that Java is, as you say, "popular" because it's forced on many workplaces by management. I don't think, in my opinion, personally, that it's a "bad" language, but its heavy use is not proportional to how "good" it is. I don't think my willingness to respond but refusal to be berated by an internet stranger is rude, and yes, I believe you have a vendetta, just like I have a vendetta against Java. If you have to use it, fine -- the above will insulate you from the really terrible things java does, in my opinion, personally. My real advice is don't use java, but see how that's a bad faith response? That's why I kept it to myself.
I'm saying that writing, maintaining and deploying Java in a manner that is fast, pleasant, sustainable, performant and desirable for developers is already at risk, in my opinion. As a developer, Java is *not* the best solution and taking away the niceties listed puts Java in a place where it is at risk of being unsuitable for the task. This isn't solely a fault of Java, some of this is simply better solutions for problems existing around Java. Java moved quite slowly for a long time and solutions filled that gap. I understand a business' desire to have a uniform programming language that they can hire for, but these days languages look very similar and polyglotism isn't that hard to find. Choosing the right language for the task is an engineering decision just like preserving language uniformity is. I don't think either is wrong, but the more people try to make Java a place that is worse than it's competitors, the more I'll favor choosing a different right language over preserving uniformity. I hope this helps clarify what I saw to be an extremely benign opinion to voice.
Java stacks will be running major companies long after everyone posting in HN today is dead and gone.
https://openjdk.java.net/jeps/321
https://github.com/AsyncHttpClient/async-http-client
https://hc.apache.org/httpcomponents-client-5.1.x/examples-a...
What wasn't clear to me was if the coroutine/nio mashup still used threadpools because coroutines do.
You can inject a property by defining it as a koin injected property:
And this is how you could create your koin context: Modules can be tied to life cycles on Android and there are a few more features like being clever about co-routine scopes. But there's not a lot more to it in terms to using it. Very simple code to write. Low overhead. It's all just function calls.Spring has a similar kotlin bean dsl that was added a while ago that you can use as an alternative to annotation processing (which is much slower).
Nothing else seems to come close, they have a Community version, nowadays Eclipse and NetBeans both feel slow but Visual Studio Code with Java plugins lacks refactoring abilities one might expect in an IDE for non-trivial projects. Also, if you get the Ultimate package of their tools, you get all sorts of other useful tools, personally i also enjoy WebStorm and DataGrip for developing front end stuff and working with databases in a separate tool.
JDK: whatever the LTS release of JDK is at the time, based on the kind of work that i do (so JDK 17 now) https://adoptium.net/
As long as you're not stuck with JDK 8, you should be fine in regards to this. But you can definitely enjoy some speed improvements across the releases as well as new language features as well as things like helpful NullPointerException messages. Personally, i'd sometimes also look towards OpenJ9 as an alternate runtime (due to lower memory usage), but that project's future isn't very clear (at least in regards to available container images) last i checked.
As for frameworks, pick one of the following:
In practice, you're most likely to see Spring Boot in existing projects since it's so boring and dependable, though perhaps sometimes you'll also run into the legacy Spring framework (which can be a pain to deal with) or even some of the other ones.Here's a rough performance comparison if you care about that sort of stuff: https://www.techempower.com/benchmarks/#section=data-r20&hw=...
Build tools: personally, i just use whatever Docker images to base the apps on when available and something like Ansible when not. For the actual toolchain, Maven is still pretty dependable, i guess Gradle is also okay. You might occasionally run into tools like Bazel or Jib, experiences there might vary.
App servers: if you need an application server for some reason (e.g. deploy app as .war), Tomcat is still a good option. If you need the EE functionality (e.g. Java EE which is now Jakarta Java), you might need to reach for something like TomEE or Payara Server, though i haven't needed to do that for a few years at this point, since Spring Boot embeds Tomcat and that is good enough for almost all projects.
After not using Eclipse for 10 years, I had to use it again last month for a very large existing project. I was pleasantly surprised. It felt faster than IntelliJ.
I last did 1-2 years ago and it still wasn't quite passable in a Java project with 4000-5000 source files, though maybe that's because of the plugins involved (e.g. myBatis for ORM which has Java interfaces and XML mappers for SQL queries) or mixing technologies like JSP/JSF and also having JS files with front end resources in the same codebase.
Of course, i'm talking about the full Eclipse with the JDT package and a bunch of other stuff, some folks have more slim installs: https://www.eclipse.org/jdt/ In that regard you can indeed have a lot of flexibility. Oh and i think that a while ago they also tried having lightweight solutions like Che and Theia as well.
Recently i've actually started splitting up the old legacy projects into multiple monorepos (e.g. "back-end" and "front-end" folders with the occasional supporting service in there as well, not necessarily everything in a single repo) and the impact has been pretty noticeable!
For starters, separate instances of IntelliJ and WebStorm for different kinds of code (Java and JavaScript/TypeScript) seem to work far more quickly, the builds also are generated faster (since fewer resources to copy) and i don't need to cry when i forget to tell the IDE to ignore node_modules.
I think the UI of IntelliJ is snappier, so for small projects that leads to people saying it's "fast".
For context I do a lot of contract work mostly in banking, ecommerce and insurance.
* IntelliJ is very good at generating boilerplate getters / setters etc
* I found sometimes the annotation processor would get out of sync with the IDE resulting in compilation issues until the processor was run again
* Java 17 records replace a lot of the functionality
Java records can help somewhat but as others point out, theay are not compatible with JPA, sometimes you get real value from Lomboks @Builder etc.
On the other hand if you combine lombok and JPA entities you can easily shoot yourself in the foot (autogenerated hashcode and equals can cause issues in some cases)
Which of course means all the old discussions: Jetty/Tomcat/JBoss/GlassFish, GSON/Jackson, Hikari/C3PO/etc, etc are now mostly moot, since most just use the Boot defaults until they need something else. Kinda nice, actually. Less bikeshedding, can get a project up and running without taking a stand on all these things.
Most clients use maven, a few gradle, some used gradle and moved back when no one could understand their config. Gradle might get a second chance now that it can be written using kotlin.
If you want to go full hipster, there is Micronaut (also Quarkus) and if you'd like to go a bit off-charts, there is Dropwizard. I don't think that they offer anything extra over SB though.
Since then it has.. evolved to something quite big, I feel, where one quickly end up with lots of unknown boilerplate in one's project. I mean, it's probably useful in its own way, almost like a Django alternative or something with batteries included. But not very hipster anymore.
Pick up Kotlin first, then Spring Boot? Or the reverse? And is the free version of IntelliJ good enough? I noticed on the features list that Spring support wasn't listed
If you have some old Java code, a good way to get started is converting some of that to Kotlin in intellij. You'll get familiar with the syntax that way and you can figure out the language as you go. Mostly things are a bit different but not that much. Nicer too. I remember my first afternoon of converting a few tests and then really liking what I saw. That's five years ago.
If you are doing a greenfield project, I'd suggest starting with a simpler framework like ktor. Especially if you come from a node.js or go background. Ktor is very nice. No annotation magic, easy to understand, and you can write a simple http service in a few lines of code. There's nothing wrong with Spring Boot (I use it), but it's just a lot to wrap your head around and figure out. Even just deciding on which bits you should and shouldn't work involves a lot of decision making. It has a lot of legacy features at this point.
If you feel more adventurous. Ktor is also quite nice and a lot more lightweight than spring. They have a pretty big 2.0 release coming up for that with a lot of nice improvements. Ktor is also a nice alternative for doing http clients. Kotlinx serialization is the way to for serialization; especially if you are interested in multiplatform. Spring supports that as well since a few releases.
If you use Kotlin, stick with Gradle and use the kotlin scripting variety. Maven is very much a second class citizen in that ecosystem. It's supported (grudgingly) but not that actually that widely used. A lot of older Spring projects might still use it because they started out as maven/java projects and only later added Kotlin. However, if you start from scratch, just use gradle and take the path of the least amount of resistance. You'll have an easier time and essentially all of the Kotlin documentation assumes you use gradle. For things like kotlin multi-platform, maven is an afterthought. I'd be surprised it works at all actually for that.
As for Hibernate, I've seen a trend of using ORMs less and less. And if it's used, it's in a simple dao layer so that the rest of the application doesn't have to deal with how data is fetched.
For many projects synchronous programming can work just fine, and it lowers complexity.
Also, if one can just wait a few years, loom will make many part of it obsolete - when blocking code will magically get non-blocking while it retains the readibility.
I've since switched over to Scala + cats-effect. I found that to be a huge learning curve, but the payoff for me has been library support for easy-to-use and easy-to-understand concurrent and asynchronous programming. I cannot recommend it enough.
Loom won't make any of this obsolete. If anything, co-routines will probably be the most user friendly API to use Loom when it comes available. Loom is a pretty low level API and you probably should not use it directly and instead use something that uses that. Like Kotlin co-routines, which is designed as a high level API that can be backed by all sorts of asynchronous and parallel computing implementations/
Extension functions exist for webflux, rx-java, vert.x, thread pools and much more. Basically, it sits on top of these things. It also works on kotlin native, kotlin-js (in a browser and node-js) and in the JVM where the underlying platform of course provides very different implementations. For the end user, it works pretty much in the same way across these platforms.
Loom will just be another low level backend for the co-routine API. It will likely give you some performance benefits if it's available and that's about it. Update the co-routine library, maybe fiddle a bit with your co-routine scope's and you'll be using Loom. I'd expect very little code changes would be needed when the time comes. Perhaps none at all actually (aside from bumping a version number).
So maybe the frontend and deployment has changed. The rest probably not so much.
Java versions 8 and 11 are by far the most common.
Lambdas were introduced in version 8.
If I was starting a new project I'd look at Jooby, which looks pleasant and does very well on TechEmpower benchmarks.
For something a bit lighter weight, Vert.x [3] is a good option (Quarkus is based on it).
[1] - https://quarkus.io/
[2] - https://micronaut.io/
[3] - https://vertx.io/
You'll need Java 1.8+, and Maven or Gradle for the toolchain (I prefer the former). Intellij is the best IDE but I get by with VSCode.
* Latest JDK
* Spring Boot
* JQuery/Bootstrap
* Eclipse (with Vim keybindings plugin)
Caveats:
* If it was a personal project or only a very small team, I'd start with Kotlin
* I haven't tried IntelliJ in quite a while and would give it a shot to see if I wanted to switch off Eclipse now
That would be a "modern" stack for me. If you can't use Kotlin, Java has improved a lot since 1.6 you will be pleasantly surprised.
I recommend you start an intellij (Free edition if you want to, even if I still recommend the paid one) and follow the springboot kotlin tutorial below. That will give you a good idea of what the ecosystem looks like these days.
https://spring.io/guides/tutorials/spring-boot-kotlin/
* IntelliJ
* Standard enterprise Java JEE 9.1 (JSP, JPA, EJB)
* Payara app server
* Twitter Bootstrap
* Maven
* Test driven development using TestNG + AssertJ
* Postgres + Liquibase for schema management
Code samples now come from https://www.baeldung.com/
* Testcontainers - seamless support for using Docker in integration tests [1]
* Project Reactor - nice framework for reactive programming [2]
* Executable Jars - No need to deploy a War file to a servlet container anymore. Build an executable Jar with an embedded container. Spring Boot makes this very easy
1. https://www.testcontainers.org
2. https://projectreactor.io
- Java 17
- SpringBoot 2.6.x
- Hibernate
- Maven
- MySQL or Postgres
- Liquibase
- Swagger/OpenAPI
- Docker
We're fans of CDI, it's a more polished Spring framework without the legacy weight. We're developing in Quarkus, MicroProfile, and bigger monoliths in Apache TomEE. We use ActiveMQ extensively for scaling. ( And I mean extensively... on a modest 512m server, we can push several thousand messages/s reliably to a _lot_ of topics and queues, all with delivered-exactly-once guarantees)
We avoid the fanfare of Docker, as really it's not needed for Java apps; they're somewhat self-contained anyway and it created more problems than it solved. For true isolation, we use systemd to create cgroups and chroots and prevent application escapes. For deployment, apps are one-jar'd down to a single executable, then packaged up in a .deb using the jdeb maven plugin. We stick with the unix philosohpy of using /etc/default for env variables that help the app locate their database or LDAP cluster.
This makes it super portable.
Just something to think about.
I personally think the win's from Containerization are greater than the challenges.
That being said, to your point, we do have some inter-service dependencies that have to run separately. We haven't figured out a good balance yet, but it's the exception not the rule fortunately, and everyone "just knows" how to run an extra app in those extreme cases.
Depending on your situation that sounds like you have a good solution that's working and you're happy with.
I personally like them a lot but it's definitely not the only way to do things.
Since dotnet core started this has been front and center in C#. So there are other stacks doing this.
I did a project using GraalVM's polyglot abilities and needed an API.
Tried Spring, Micronaut, Helidon, Ktor and Quarkus.
Quarkus is the best web framework I've ever used, in any language. Can't go back now.
It's so well-architected that I was able to contribute an extension for Scala 3 support within a month, and the Redhat employees have answered every question + issue I've raised.
Being built on top of the Microfile spec and using Vert.x is a hell of a combo. Vert.x is the best thing since sliced bread too.
Can't recommend Quarkus enough, whether you write Java or Kotlin or Scala (I'm a big Kotlin fan myself).
Admittedly the install instructions for Gradle are a bit dated. Gradle 7.4 shipped with Scala 3 support.
It's exactly the same in the .NET space
If you use a language that doesn't require OOP, you can do the same isolated unit tests without DI.
It's a specific category of OO architecture, where effects and state are buried under domain logic and information processing (via DI). If you keep that part data oriented and pragmatically functional, then you get a very testable core. Meaning no mocks, stubbing setup/tear-down (...) are required. No additional interfaces have to be specified that you didn't need in the first place. Tests are way simpler this way.
You can still use objects, interfaces and composition etc. for domain logic and information processing if you want. Point is you lift state and effects up, so this stuff happens at the edge of your program that calls into a simpler core.
If it takes too much effort to write that anonymous inner class, then the Interface may be too big and need broken up. Or maybe you're hiding too much state and need to rethink the API.
What does this mean?
For example, if you're writing code in C++ targeting Linux, using Docker helps with reproducible builds and deploying on any hardware.
But if you have a VM, there are less advantages. There still are some, of course, with orchestration etc if you want it.
Now, a lot of complicated bits were inherited that didn't need to be there when they brought over the mainframe thinking into Java apps, but the WAR format was actually something kind of cool they got right.
We in particular, take that WAR format, turn it into an executable, and we have a program that can execute on any hardware on any person's computer, with no pre-reqs except a JVM being installed. We test the _exact_ war file on a ARM RPI , developed on a Mac m1 or x86, and CI on a Linux x86.
Alright what does this mean? Lets take a look at another language...say a PHP application.... which may require extension Unix utilities of certain versions installed on the host system. You may need a certain version of PCRE grep, a certain version of curl, etc. This theme carries over to multiple languages, your python or ruby implementation may not support the latest version of libressl, or it might not even support your ABI at all. Docker solves these problems gracefully, by providing a fully configured virtual operating system tuned for running exactly one app.
The JVM... well doesn't need that, if written properly. Generally all the libs it uses are written in Java, they include all of their dependencies already, apps can be isolated with classloader boundaries (if that's your thing), you can download a single file and launch it if you use the "one-jar" technique.
So yeah, Docker loses a lot of it's luster with Java. It's not that you _cant_ use it, but it you can solve all the problems it does otherwise. Long rambling explanation but there you go.
The most common: - Kotlin for most of the backend code, but Java for shared libs. We still use Java 8 for some libs which may be used in Android, that's an unfortunate reality
- Gradle for build config
- Spring for application architecture
And few things that may significantly improve your dev process, but are not so common:
- Spring Reactor (and Webflux) for processing data and requests. Takes a time to learn, but it worth it
- Thymeleaf for UI
- Spock for testing. Highly recommended for designing tests
- Testcontainers for integration tests
- Micrometer to see what's going on with your app. I.e., to export all internal metrics to use with Prometheus/Grafana/etc
For the environment
- SDKMan to manager the environment
- Gradle Application plugin or Google's Jib to pacakge your app. First prepares a Zip with all binaries, second a Docker container
- IntelliJ IDEA
- Github Actions - turns out to be the most usable CI. Though the Jetbrains TeamCity may be better for a large team
Considering Kotlin is a supported, first-class language on Android, why not write your shared libs in Kotlin as well?
So I think that Java is the best middle ground here because it guaranties the full support of JVM features and 3rd party tools. I mean just avoiding a risk that it would be incompatible with something.