Generating a Java program with 90% less code
unitily.com
unitily.com
It always seemed weird to me. Like if my house was really cluttered but instead of building shelves or closets into the house itself, I buy an elaborate conveyor belt and excavator system to rearrange all my things all the time so I can walk through all the mess.
Whereas in huge JS codebases relying on framework magic you can really have hard time to understand what's going on without plugging a debugger (and even then it's still not easy). Trying to refactor legacy code that relies on `this` and prototypes is a nightmare.
Ruby Example:
Ruby projects tend to always have really well maintained test suites. The language makes it easy for you to introduce non-obvious bugs or write unreadable (yet efficient) meta code.
This has lead to 1) very mature testing frameworks (meh) and 2) high adoption and usage of these frameworks (actually impressive).
Funny enough, I think adoption of IDEs is extremely low in the Ruby world. 90% of the developers I interact with in my city are on Sublime/VSC/Vim. I've seen some pretty impressive usage of Rubymine that has made me curious, but never bothered to really spend time with it myself. I'd be really curious what the teams at GitHub/Stripe/[Insert big Ruby company] typically use editor-wise.
That’s an assumption which advantages Java by default. Other languages may be able avoid having a large code base entirely. I recall reading some stories of people rewriting giant Java code bases into very small Clojure programs that were more flexible, maintainable, and scalable.
Granted it isn't 10s of thousands of lines, but it isn't small either. It does a lot of things ranging from webapis to data pipelining with ingestion and egestion of large datasets. One batch job can result in 10s of GBs in output files. It has to be performant too, 10s of millions of "operations" in one batch job is common.
Admittedly the process/threading model can be painful to work around at times, but totally doable, and is honestly somewhat friendly to distributed systems to an extent because it somewhat forces you into an actor model paradigm.
I disagree. Most people working on the Linux kernel (including Linus) or the major GNU projects (including RMS) are using vim or emacs with maybe some supplemental find/grep/sed/awk.
(Yes, I know you can do Java in VS Code but I wouldn't want to manage a large Java codebase with it.)
“Code should be written for humans to read and only incidentally for machines to run” (paraphrasing a famous quote by Harold abelson)
I think the conundrum of java is not quite the same thing as strictness.
* Eclipse has a crappy UI and can't handle Java9+ code very well, but can handle large code bases
* IntelliJ Ultimate has nice UI and assistance, but it totally fails with large code bases and still tricks you if you run code inside the IDE, especially when you want to use jigsaw modules and not classpath, as it uses by default
Unfortunately, using Java with other editors (vim, VS Codium, etc) is just difficult
An example: if I want to refactor a simple thing like Rename a method called “doFoo()” I can do this trivially.
Now, this seems simple but it’s not - I only want to refactor uses of this particular method, NOT any other methods that happen to be called “doFoo()” on unrelated classes or interfaces. Moreover, I want the refactor to intelligently work on everything, including inherited classes, abstract classes and interfaces, while correctly ignoring the stuff i mentioned previously.
None of this matters much if you have a small codebase and a fee developers, but on a huge codebase with many developers, all that boilerplate lets people quickly understand new code and easily refactor that code while being sure they did not break anything.
I don't believe this to be true at all, especially for large code bases. Often the people who support this position have not worked on 100k, 500k, 1m LOC applications I've found.
Boilerplate makes it much more difficult to read and scan large swatehs of code. I think when people say boilerplate 'helps', they often noticing a correlation between the fact that many strongly typed systems happen to have excess boilerplate. I believe it's the typing that helps, not the boilerplate.
That being said, IntelliJ's IDEA is fantastic at hiding a lot of the boilerplate in Java (like getters and setters and all those import statements at the top of the file) and modern Java is FAR FAR better about being less verbose than it was in 1.0
Java also has Lombok (https://projectlombok.org/) that hides almost all of the repetitive stuff completely.
The real reason to move away from Java is to move towards different software architectures (microservices!)
This is a SpringBoot microservice:
@RestController public class BasicController {
@RequestMapping(value="/{id}", method = RequestMethod.GET)
public String get(@PathVariable("id")Integer id) {
return doStuff(id);
}
}I'm not sure it's any more verbose than the equivalent Ruby on Rails microservice (for example).
This distinction is reflected in even small design details. For example Java programs are comparatively slow to boot up. If you're mostly used to the Unix way of repeatedly piping the STDIN/STDOUT of small utilities in an interactive command line, this is painfully inconvenient.
But if your workflow involves a monolithic server that manages every component of the business logic in a single process, then 5 second boot times aren't really a big deal. Because maybe you bounce the server once a week at most.
This always confused me as a newer developer. Vendors competed with each other with different JavaEE implementations (WebSphere, Glassfish etc) that all conformed to the same interface and provided everything including the kitchen sink, but what really differentiated the products were vendor-specific features that require you to go outside the interface, but if you tried to use that stuff, another senior developer would tell you that we can't marry our implementation to a specific vendor... meanwhile, literally every company you go to uniformly uses Spring and Hibernate, and both will inevitably require you to not pretend that they're dumb implementations of javax.inject and javax.persistence.
Small programs can be written in java. It's a general purpose programming language. You could write grep in java.
I'll give you this though: Old Spring and JSF code could be really bad, I remember updating two different XML files every time I changed something in a controller.
Edit: even back then with all that boilerplate it was really nice compared to legacy Delphi where it took three full days with an expert to find and install all required dependencies before one could even start compiling the old source.
By comparison my Java dev environment was up and running in less than half a day with Ant, and when we introduced Maven a few months later setip time for a new developer was mostly the time it took to check out code, install dev databases (yep, back in the days) and set necessary environment settings. (Protip: many people hate NetBeans but one thing it got more right than all other Java IDEs AFAIK was accepting the pom file as the truth instead of creating its own.
Sometimes project is big enough to have a lot of code, boilerplate or not. And then all that IDE automation suddenly becomes essential.
I’ve been to some huge Scala codebases, it was much harder to navigate or make changes there than shove around Java cruft with IDE.
I like being able to consider a shell to be the lowest common denominator for a codebase. If basic shell commands, environment variables, a vanilla vim/nano editor etc... can’t be used to hack on your system then I think it’s far too coupled to tooling.
I use Sublime with a handful of plugins, linters, etc... enabled. But if someone wanted to hack on my projects with Notepad they totally could.
i.e. if you can't (realistically) choose your own tools, then your experience with the ecosystem is that of your experience with the tool. No tool can be a good experience for everybody.
The above does not evince a fear of modern text editors. Nor does it equate to clinging to 1980s technology. Why do you think it does?
Just because something was created long ago does not mean it hasn't improved. Vim and Emacs are both extremely featureful and powerful editors and many people prefer to be able to pick and choose specific tools that do their jobs extremely well.
It's not about using "old" technology - it's about using a tool that excels at its targeted task, and letting you choose the best tool for ancillary tasks like searching, formatting, testing, debugging etc.
I mean, you get the same result, sure, but I find I can hardly ever rely on test coverage alone as an accurate safety net to clearly indicate whether I've reached an actual stopping point for a full refactor.
This is one of the ways in which Java excels, in that that loop is fully accommodated for in most major IDE's.
You can still get caught by surprise at runtime, but I've found that for a strongly typed language like Java, the syntax, and necessity of boilerplate makes most bugs obvious until you start getting overly dependent on frameworks you don't understand.
Python is not static typed, it won’t refactor as easily.
The result is an excellent toolchain. But also you are completely unproductive unless you master said toolchain.
That said, the code is usually there for a good reason and I'm extremely thankful for it in the instances where things are refactored and the compiler catches it, or some deviation from the norm comes up and the scaffold allows for doing things the way you need.
Are you using an MVC web framework, or something like that? Asking because I'm curious about other people's experience with Rust.
Writing code without dependencies/frameworks that have a required structure is a joy, however.
Not that I think that's a bad thing. I prefer it to "magic" (I never got on with Rails because of this... what just happened? why did it do that? where does it say it should do that?).
And you never know when you have a class/use case that needs the boilerplate to change. Having it the same 99% of the time so that you have the option to change it for that 1% edge case is a good thing, imho.
With Spring you get to ask all these questions...with all the boilerplate of Java as an added bonus!
The idea is that you hire an army of marginally qualified developers through an agency that are making $14-18/hr in the US and have them churn out code.
To use your house example, it's like addressing clutter by having 5 children and making them clean.
The best programming languages for developer productivity are those that are designed with tooling in mind, instead of grammar + semantics and then we see later how everything evolves.
Another problem i had with java: most projects are impossible to understand by looking at the source code, as they tend to do things like Spring or EJB: with delayed loading where class names are specified in some configuration files - and it is often hard to figure out how the system is initialized.
That leads to another question: Is it possible to learn about an unfamiliar java project from source code in this day and age?
i mean they used to say that perl is a write only language; but enterprise java isn't very readable either.
Yes it is, but it helps a lot if you're familiar with the framework and the original developer used sensible conventions. I guess that's the same with any language though.
For me it seems weird that the Java IDE is "smarter" than the compiler. Then why can't the compiler abstract stuff...
Like the 'auto' keyword that took how long to exist again?
I'm strongly against creating interfaces for non-library (not shared) code, that will only ever have one concrete implementation. I see it all the time in internal service code, code that will never have a second implementation. It makes code hard to navigate, harder to understand, and tedious to manage as instead of updating one file, now you'll need to update two.
If you eventually need that interface, you can change your existing code to use it. Making that change is relatively easy compared to maintaining a bunch of code that isn't needed. But still I find it hard for some programmers to get past that mentality.
I can't count the number of C#/Java projects I've walked into where it felt like I had to drill down through a dozen layers of abstraction just to find code that actually does something. Hundreds (thousands?) of files that contain nothing but autogenerated boilerplate crap.
Layers upon layers of boilerplate, "just in case we need to swap something out later!" No. I promise you, you won't. And if you do, I promise you things aren't as loosely coupled as you think they are. You're going to have to do a bunch of rewriting anyway.
Also, 9 times out of 10, by the time you realize some seemingly interchangeable piece of your architecture needs to be replaced, your team will be chomping at the bit to blow the whole thing up and rewrite from the ground up anyway.
In the meantime, having a simple codebase that people actually enjoy working in will make everyone's job so much easier. And fun.
In my own progression as an engineer who writes Java, I read Clean Architecture by Robert Martin which lead to AbstractFactory(s) and interfaces up the wazoo. The reasoning would always be "this can change!"
It definitely took me some time to understand that not everything will change, and if it does change, having some extra work for simplicity is a reasonable tradeoff sometimes. In general, knowing when to utilize OO design patterns takes time and experience.
It took a long time before I realized that a lot of the changeability that come with writing aggressively SOLID code is actually a self-fulfilling prophecy: Some tightly intertwined chain of steps that might have been implemented as five consecutive (albeit tightly coupled) statements gets blown out into 10 or 15 modules and abstractions spread across as many files. All this added complexity was invariably designed to make it easy to change the code in a way that is not actually how you ended up needing to change it, so the end result is that it increased the difficulty of changing things: Instead of editing 5 lines, you now need to edit 10 files. And, since the logic is spread across 10 files whose members are, thanks to the magic of Java's access control facilities, all public, and whose connections to other parts of the codebase are all, thanks to the magic of dependency injection, loose (read: not explicit), it's hard to even understand the scope of impact of those changes without relying extensively on an IDE.
I've since taken a big turn back toward procedural programming. Object-oriented and functional techniques have their place (and the best procedural languages will let you use both), and I've learned a lot from working in both paradigms. But most of what I learned is that no amount of abstraction can replace the KISS principle.
As I understood Uncle Bob's argument for using interfaces though, the main benefit is not supporting alternative implementations, but rather aiding in the enforcement of the Dependency Rule[1] and of the Single Responsibility Principle[2].
Interfaces make the relation between components explicit in two ways:
- they make it clear what depends on what - they make it clear what are the responsibilities of the components on either side of the interface
So aside from the fact that it might make it easier in the future to swap implementations, it first makes it easier when writing the code to decide how to organize its components (i.e. laying out its architecture).
Of course I agree with you that interfaces can definitely be abused, and can make code more indirect and difficult to read. I just wanted to point out what I think is Uncle Bob's point. :)
[1] https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-a... [2] https://en.wikipedia.org/wiki/Single_responsibility_principl...
Abstractions are not evil, but it takes experience to use just enough abstraction.
It's kind of like how cars are implicated in traffic problems. Everywhere you have heavy traffic, you'll find cars.
It takes a conscious effort to just move forward, and it still leaves a lingering thought in my mind about what I'm doing wrong while building exactly what I need.
Interfaces are one of the more powerful abstractions available and should be well thought out points of flexibility you specifically designed into your solution. If you use interfaces for other reasons your codebase is full of false flexibility and not-actually-abstracted abstractions which makes it difficult for other people to figure out what they can actually do with it.
This avoids having to introduce the extra interface, having to code the mock, sometimes having to change the mock or tests when changing the code and also lets you catch bugs in the dependencies that the tests on the dependency itself missed and that would otherwise show up in production.
One might have to do some work to partially replicate the production setup in testing and making it fast to start and run, but that's usually less work and gives a much better result.
The rationale I'm given is generally one of two:
1. "It's best practice"/"it's clean architecture" - which is basically dogmatic cargo-culting
2. To support mocking in unit tests
The later is also found with unit test projects rammed with mocks, making for brittle, unreadable tests that often don't really seem to actually test anything!This is the worst of all possible worlds, because a test suite should accomplish two goals: identify bugs and enable refactoring. Tests like these fundamentally can’t identify bugs because they don’t test any behavior. And worse, they prohibit refactoring because any meaningful code change will alter what functions are called and in what order they’re called. So any refactor breaks almost every test, and it’s impossible to quickly tell if something “real” was broken or if it’s just an artifact of poor testing.
UserController -> UserBo -> UserBoImpl -> UserDao -> UserDaoImpl.
Needless to say the implementations have never been replaced with something else. The middle layer is completely useless. UserBo, UserBoImpl and UserDao should be tossed into the garbage. Every single time you wanted to add a new SQL query you had to edit 5 files... Code navigation features of your IDE become useless and you are better off with string based search.
I was just telling someone this yesterday. Not every damn thing needs to be an interface you implement if you're only implementing it once. Especially if you have to put "I" in front of it (as is standard in C#) and name the class the same thing, you should think if it's even necessary...
Even if it does, for a mild behavior change or a test implementation, the simple fact that Java methods are virtual-by-default is a huge benefit here.
C#, on the other hand, is final-by-default. This makes code much more annoying to deal with unless it implements an interface and/or is thoughtfully written with these cases in mind.
What people sometimes forget though is that any implementations of this in your tests are also concrete.
Some things like a DB accessor, will use a mocked/lite implementation in testing for code that doesn't need access to the db (but still needs to be provided data) to test it's functionality.
That's the litmus test I use to determine if I need an interface: Will the code compile without it? If not, the interface is declared alongside the code that depends on it, and the implementation is in a second compilation module which depends on the first. Inversion of control.
Yeah, not good.
And you meant update 3 files, not 2. You forgot the 1 file that uses the 1 implementation, which should have simply been nested the only place it was used.
I agree with this, but the problem I see is that people have bad judgement when thinking about if something will only be implemented once or not.
Personally, I don't think the boiler plate / interface bloat is as cumbersome as others do. I'm currently working in a code base where things were decided to "never change", and now we need to change those things.
In a perfect world, I'd rather have less abstract code that never changes. But if it's a choice between poorly written highly coupled code or poorly written code that relies on interfaces, I'll take the interfaces and their bloats.
However, I personally like interfaces (well, traits, I'm doing scala), because it makes it much easier for readers to understand what bits are supposed to be part of the interface and which parts are supposed to be an internal implementation detail. Without interfaces it's really easy to accidentally change e.g. a return type into a more concrete one which accidentally leaks impl. details and suddenly other bits of code will come to rely on those impl. details. I work on rather large/complicated systems and I've seen this gradual spaghettification time and time again.
Disclosure: I'm usually the person who gets called in to fix the mess -- so that might bias my perceptions here.
OP is probably referring to the JDK Proxy support which is used by Spring and requires classes to implement at least one interface.
data class User(val username: String)
data class Config(val userTable: DynamoTable, val userId: String)
typealias ItemToUserConverter = (Item) -> User
class DynamoDbUserProvider(
val configProvider: ConfigProvider,
val userTableProvider: UserTableProvider,
val dynamoReader: DynamoItemReader,
val itemToUserConverter: ItemToUserConverter) {
fun execute(id: String) = itemToUserConverter(dynamoReader.execute(userTableProvider.execute(config), id))
}
Plus it comes with great debugging/refactoring/IDE support, async/await, type inference, and good Java integration.Kotlin isn't just more concise Java. It sets out to solve a whole set of other problems such as removing use of null pointers, enforcing immutability everywhere, etc. This has a dramatic influence on design philosophy. I'm sure if that's how you would have programmed java then its great. But if you actually just program Java normally (mutable state, null is acceptable, etc) then something like Groovy is a much more natural fit.
But I write Kotlin all the time and nullness checking is one of its best features. I wouldn't want to go back.
So I'd agree with that statement...
> do yourself a favor and use Scala
...but not that one.
I've found that Scala gets too expressive, much the same way that writing production code in Common Lisp or Haskell can be slower than just writing it in Python. It offers incredibly powerful abstraction capabilities that can often dramatically reduce the amount of code you write; but at some point you end up spending more time hunting for the perfect abstraction than actually writing the code. The ideal programming language (for productivity at least, not necessarily for fun or erudition) is one that melts into the background when you program so that you can focus on the problem domain rather than the program. That's the main appeal of languages like Go, Java, and Python: they offer just enough power to write the program, but not so much that you can write the program in a dramatically better way and end up tempted to try and find that way. Kotlin embraces the "it's just a better Java" approach: it's a minor re-skinning of the concepts in Java, and to the extent that it introduces new language features (like closures, lambdas, properties, data classes, anonymous objects, type inference, and immutability), they're all concepts that skilled Java programmers would be immediately familiar with but would otherwise have to write lots of boilerplate for.
The Java interop story is also better with Kotlin, because Scala introduces abstractions for which there are no obvious Java equivalents, and hence there's an impedance mismatch when you try to envision how your Java libraries might fit into your Scala program. Kotlin's designers took care to make sure that there's an easy, intuitive mapping between Kotlin concepts and Java ones, usually one that had already been enshrined in existing Java conventions (eg. JavaBeans work nicely with properties, functional objects with lambdas), so most Java libraries just work in an obvious way. Basically they intentionally made it dumber so you don't have to think about as much, which is a win for everyday programming.
In actual use, I find the two to be a night and day difference. Scala is actively, desperately straining against the confines of the JVM. It's managed to implement some semblance of every major feature from Haskell on top of the JVM's object-oriented core, but it often has to resort to clever hacks in order to do so, and its language features are a rat's nest of edge and corner cases.
For example, it takes Java's already confusing type system where most, but not all, things are objects, where some types can be reflected on, but not all of them, and where there's even a special annotation to help programmers deal with legal code that the compiler is somehow unable to type check, and it tries to render it more manageable (I guess?) by adding even more concepts to the mix: Class tags, type tags, and weak type tags. The end result is. . . well, if you really, absolutely must higher-kinded types and also the JVM, I guess that's as good as you're going to get.
Personally, I like Scala. It's my main language right now. I want an ML-style language, but am trapped on the JVM, so it suits me well enough. But if others want to observe that, no matter how much you might like mustard, it's still not a tasty thing to put on pancakes, well, I can't in good faith say that they're wrong.
Kotlin takes completely the opposite approach: It accepts that Java's decision to use an unholy blend of type reification and type erasure can't be unmade, and then focuses its effort on unifying the type system. Which is the kind of conservative and pragmatic approach that arguably represents the soul of Java. If you're looking for a revision and not an outright revolution, Kotlin is more likely to be your jam.
No thanks. Scala had a shot these past ten years but it failed and the world has moved on.
80% boilerplate is a large exaggeration.
Culture is really important in programming languages and I'm not sure it is something you can ever separate from the language itself, as for example the standard library dictates in large part the structure of your own code.
I do agree though that there is no great reason Java has to be more verbose or more confusing than say Go.
Does Go have objects with functions? If so, it has getters and setters, Go programmers just choose not to use them. I could absolutly write Python code like this:
class MyObject(object):
def set_foo(this, foo):
this.foo = foo
def get_foo(this):
return this.foo
The thing is...Java doesn't have getters and setters. There's nothing about the language that requires them. This is perfectly legal Java code: class MyObject {
public int foo;
}
Look at it! It's a public member! You can read/write to it without a getter/setter! You know, something every language has, but for some reason, it's considered verboten in Java.Java is a perfectly fine language. It's Java programmers that are awful for sticking to these unnecessary paradigms that necessitate ridiculous amounts of boilerplate.
Maybe conforming to an interface without explicitly declaring it (like you declare an instance of a typeclass in Haskell) may look like a controversial decision. I still think it does more good by removing boilerplate than bad by accepting an object as conforming to an interface in a rare case when you did not mean it.
Not a Go developer, but two things off the top of my head:
1. Do not need to explicitly declare interfaces a struct conforms to.
2. Do not need to declare types of variables inferred on initialization.
3. Syntax for slice, map and struct literals.
I'm sure there's more...
Unfortunately, inheritance is deeply ingrained into many libraries and standard classes, though it has been replaced by interfaces in most standard APIs.
Yes, the boilerplate culture is a problem, not of the language but of the ecosystem :-\
You can stay reasonably away from it in many cases with modern libraries, though.
Could you please provide an example in both Go and Java? I am not quite sure that it has to do with the type system itself but your familiarity with the language and its libraries, and/or lack of basic functionality that should be in your text editor when you write Go.
value := getValue()
Java:
int value = getValue()
If you're writing a lot of boilerplate in Java, you're either doing it wrong, chose it for something wildly out of its wheelhouse, or you choose frameworks or libraries that have either forced or afforded high boiler-plate code.
The latter is probably the major difference that is what people are feeling. On paper Go and Java are virtually the same language, but just a few differences in the spec and a bit of focus by the developer community means that Go just generally has less annotations, external XML files for this, weird build systems, and a whole bunch of other frippery.
Although I do think that it's easy to underestimate how much cleaner it turns out to be to do interfaces the way Go does rather than Java. It seems like such a small change, but it has a big effect on how much OO boilerplate you need. You don't have to guess what interfaces someone may someday need and write them all out in advance.
And, I mean, in both languages, let's not underestimate the amount of "doing it wrong". There's a lot of us whose exposure to Java is being on the receiving end of a pile of Java code that is not written with maximal skill and precision. That's not really Java's fault. (To the extent it is, it is not uniquely so; I can tell that story about a lot of languages.)
Like shoehorning a set use case into a map? Or trying to get the list of keys or values in a map without writing out 5 lines of code?
I find golang code to be much more verbose than Java. You find code littered with one off functions that amount to nothing more than map/filter/etc. operations which are 1 liners in Java.
(I am underwhelmed by the crowd that insists map/reduce/filter is so important anyhow. I've used a substantial amount of Haskell. In Haskell, map/reduce/filter is the beginning of the functional style, not the completion of it. It goes beyond just "an alternate way to write for loops", which is most of what I see non-Haskell usage doing with it. In Go... just write the for loops. It's the same thing, just spelled differently. In Haskell, it's not the same thing.)
I'd have preferred much lambdas just exposing checked exceptions they experience inside. I can't imagine why the compiler shouldn't be able to do this. Maybe one would have to come up with a fancy definition of functional interfaces (although I think this could be tackled with an annotation if the language doesn't provide this for lack of another keyword a la "throwsall").
If you really want something like checked exceptions then (anonymous) intersection types or row polymorphism would be much better approaches than the adhoc thing Java does... but I suspect that that wouldn't well with the variance system in Java.
(Thankfully it's only the Java compiler itself which imposes checked-ness, so other languages on the JVM thankfully don't have to deal with the insanity.)
if e3 != nil {
return _, e3
}
over and over and over..If you restrict yourself fully to the standard library it's still not that largly exaggerated, even with Java 8.
> The compiler has a well-defined custom processing interface (@annotations) which allows for powerful boilerplate-reducing things, from Lombok to Spring to JUnit.
If you add Lombok + sth. like guava or apache-commons and in general choose modern libraries/frameworks it's really OK now. The only downside of Lombok is that it really maxes out what compiler(s) allow you to do which is not always without issues and it requires an IDE plugin to be installed.
In contrast, when I started using Python, one of the things I liked was how suitable it was for all the things that Java developers typically used other languages for. Your typically Python project back then contained very little that wasn't Python, because there weren't many things that it wasn't useful for.
Things may have changed with Java in the 9-10 years since then but I think that was certainly one of the things that led to Python becoming more popular.
Other competing languages you would use today didn't exist, and C++98 was a pain in the ass. The selling point back then was Java was C++98 with all of the difficult parts stripped out, and with good tooling and IDE support. Back then people flocked to Java like some sort of oasis. Today, even modern C++ is better in most ways.
I came back to java about 4 years ago after a long time away and have found it quite pleasant, and quite different to how you describe it.
I think I skipped the 'enterprise' years, thankfully!
You can write verbose garbage in any language and you can write clean, well factored code in any language.
The garbage I'm most familiar with is "Consultant Java" where every class has an interface, every field has a getter and setter, every method (especially the getters and setters) has Javadoc -- oh and the test coverage is 100%, thanks for asking. But you've got 450K lines of code for an app that does CRUD on 3 tables. I've seen virtually the same thing in C#, only with the added magic of the latest MS Framework. And if "boilerplate" is such a bad word, how did Ruby on Rails ever succeed. It's an environment literally built on boilerplate. And don't get me started on perl. There are perl scripts that were healthy until they kept dividing until they were cancerous "systems" throughout the enterprise. C++ pissing contests between developers intent on showing their worth by writing the most difficult code possible.
So why do have so much garbage out there?
Because programming is a human endeavor. We have tastes. We're often unwittingly subscribe to faddish beliefs. We all suffer from a host of cognitive biases that will always prevent us from seeing things clearly.
I can say with certainty that particular tools are better suited for particular jobs. At this point though, you can't pin me down as to which for which. I can say that in the past I enjoyed using C and Java and I didn't enjoy C++ and C#. I was lukewarm over Python. I currently enjoy using Scala and I think I am most productive in it. But that is more about me than the languages. Importantly though, I think they all contribute to the software industry in ways that are not always obvious.
This is not remotely true. "Don't Repeat Yourself" and "Convention Over Configuration" are the two core tenants of the Rails philosophy[1], both of which are intended to reduce boilerplate.
This is why you don't have to specify table names, foreign key names, controller endpoint paths, view file names, etc...
[1]https://edgeguides.rubyonrails.org/getting_started.html#what...
1) the code had no external review, was written by one person. In this case, the solution to boilerplate is code review.
or 2) if even with code review there is boilerplate, it means someone somewhere has an incentive to make the code as difficult to understand as possible (for example, to justify salaries of an entire team, or to justify more maintenances, etc)
The code worked, but 80% of it added no value.
My beef is with Spring. Spring seems to make simple problems very complicated. Its like you have to learn a second more complicated API on top of the original. Plus when something goes wrong you get some 100 call deep stack trace. I just wish there were more pure Java applications without the Spring magic.
Also I hate that I need to literally use a program to create the boilerplate for a new spring app. And I can’t run my program without special IDE settings.
Edit: for example, Python “annotations” are generally just functions that return wrapped functions/attributes/classes/whatever. Java annotations seem significantly more “magical” than that.
For example for a rest service, https://spring.io/guides/gs/rest-service/ has everything you need to get started. A simple pom with a few dependencies, and a main class that invokes SpringApplication.run. You can just invoke this main class from your IDE like any other java application.
> I imported that autoconfiguration library and everything works!
> Damn it, the imported autoconfiguration library does not work (or I need to change something), now I have to spend a day around Spring docs and stackoverflow to find how to get it working. Than I have to create a new class, override some random methods and inject my ugly code which feels like a hack now...
Frankly, after some recent development on ES6 + node + express, I'm starting to question whether staying on Spring + Java is worth it.
Since then, J2EE and EJB led to a lot of boilerplate and now everyone thinks class and member names in Java have to be at least three or four words long.
And I wonder how much of the "compactness" you describe is simply because of Java's library. Library calls are very compact; having to write the code yourself (because your ecosystem doesn't have that library) is much more verbose.
... that also doesn't actually benefit you in any tangible way.
This. My god, this.
You can write concise pure Java if you don't rely on libraries, have unit tests, or do things the Java way.
It's too bad, too, because there are some things to love about Java, really. It's simple, it's super fast thanks to the jvm, and it's well supported.
I don't really ever run into the same problem twice. I've never been able to write anything as concise for a one-off problem as I can using Python or Perl (or even Awk) for data wrangling.
Java is for enterprise scale applications, where dozens or hundreds of developers contribute to the code base, for a decade or more.
The boilerplate, the verbosity, strong types, the dependencies on complementary tools is what makes a large code base maintainable in the long run. If cared for.
If you need to scrap a website a couple of times, convert text from one format to another. Yea just use some script. Throw away code that nobody but you will ever need to touch or even look at. It is possible to write a python, Perl, JavaScript, or even Php app at scale. It may require a bit more due diligence though. Frameworks have been introduced to help with that.
The article is written by someone who doesn't fully understand Java. No need for interface to support injection. Interfaces are commonly written by CS graduates or uninformed long time OOP developers, but composition and DI have been around for over a decade. I stopped reading when the interface was pointed out as the only way to go.
Uhhh... you should have unit tests no matter what language (or culture) you're dealing with.
Hum... No, you can not. Java is missing basic tools for abstracting idioms, break your code semantically, and reusing your code. Without those, you'll just write stuff again and again, hope you have a very good IDE.
> It's simple
It's simple the same way Go is simple. The language itself is small, in ways that make your code complicated. On this competition, Brainfuck is unbeatable.
> it's super fast
C, C++, Rust and Fortran are fast. Java is not. It's a second tier, at around the same speed as C# (obviously), Go, and Haskell.
Such as?
It has relatively recently gained usable high order functions, and seems to have stopped there.
to what baseline are you comparing it to?
Rust, C, and Go appear to be on the top.
- Annotations are overused.
- Design patterns are overused.
- Too many layers, too many abstractions.
- Using properties where a constants would be enough.
- Using concurrency when you don't need it.
Probably a cultural problem more than a technical.
Good quote. I used to struggle with this feeling when I got to roughly the ten year mark professionally and finally started to feel like I knew what I was doing. But, with the help of a little Stockholm Syndrome perhaps, I've learned to embrace the boilerplate as part of my workflow and use it to an advantage. I came up with a saying "write twice, debug once", from the old carpenter's saying "measure twice, cut once". The idea is that if you're writing sort of the same thing twice, the compiler and/or PR reviewers can at least check if those two things are consistent with each other, and debugging is more straightforward instead of a long slog. You may still write the same thing wrong twice, but that's much harder to do than writing it wrong once. I think this is a hidden value of automated testing as well. I could talk for hours about whether automated testing is "worth it" depending on the project/language/team/etc., but one thing most people don't consider is the value in the mere act of writing out some logic that must be consistent with other logic, and the inconsistencies you can find without even having to run the test. Essentially another "write twice, debug once" scenario.
I know that's all kind of vague, and I don't have time to get into specific examples, but this encapsulates the feeling I have now working with Java. The feeling that I'm sort of fact-checking myself, even though it's tedious. Sort of a "show your work" thing.
Not sure why a "just plain data" approach couldn't have been taken so that instead of:
@Option(names = { "-v", "--verbose" },
description = "Verbose mode.")
private boolean[] verbose = new boolean[0];
You could've had something more... Option<boolean[]> verbose =
Option.<boolean[]>newBuilder()
.names("-v", "--verbose")
.description("Verbose mode.")
.default(() -> new boolean[0])
.build();argparse4j has existed forever and its a port of python's argparse [0].
Annotations work really well: they are easy to write, easy to write and typesafe enough.
In each case (and presumably many more) you can just go and write a unit test for each of these disproving them.
-that generics don't play well with primitives and arrays?
I understand that jep218 aims to fix the first one. But it's been quite a few years now and why was this not done the right way from the start?
> equals() and ==
I think these should've been switched.
A general principle in languages (not only in programming languages) is that things that are used often should be short.
Plus
- get() and charAt(), instead of [].
just why?
- verbose naming conventions:
Excessive redundancy does not increase safety or improve maintainability. Having one blinking red light is useful, having a thousand lights of different importance will just make you filter it out. People will simply gloss over and skip long identifiers.
we are humans and we can't operate at full attention the whole time. if something has low information content, we simply go on autopilot.
Easy things should be short, so that the hard things popout in the code.
- Obsession with software engineering and OOP Principles?
Instead of thinking about the data structures needed and the alogirthms chosen, time is spent just making a millefeuille of abstractions.
Generics are built around type erasure and as such generics treat the type as Object at runtime. Primitives cannot be treated this way. The reason its not a simple fix is because the language designers are not willing to break compatibility to make this happen.
The style complaints are an artifact of Java being a popular corporate language but there's no reason you need to write Java this way.
When I started learning C#, less than a decade ago, best practices were "Uncle Bob" style nonsense, ultra-fragmentation of the codebase, interface everything, boilerplate everywhere, pointless mocking and testing of components which should have been treated as purely internal. But you look at what changed in C# -- generics, dynamic, LINQ, async, auto properties, etc. -- in the last few versions and it's a brilliant language that can be written as verbose or as golfed as you please (I lean now towards verbosity and loops over LINQ just because while I think LINQ makes you feel clever it's also harder to reason about).
Now I don't do Java other than porting a Java codebase to C# but I'm under the impression Java has been on a similar journey with streams and whatever else. Much like PHP or C++, workhorse languages that aren't 'sexy' but are widely used and keep the internet and society running.
Different people like different things and that's ok (but assuming this new language from the advertorial doesn't support autocomplete, easy refactoring, whatever else it's going to be a hard sell to get productive teams to switch over to it!).
I know bashing java for boilerplate and verbosity is popular but a lot of its reputation is people doing repetitive stuff without reflecting on better ways to do these things. I see the same patterns and mistakes in other languages. The best code is the code you don't need to write.
Likewise needless abstractions and over-engineering are a problem in many places. Java certainly has seen its fair share of that and its a reason I avoid the entirety of the Scala ecosystem in principle. Nothing wrong with the language but it seems to provoke over-engineering.
Let me emphasize that, there: "classes we get for free".
"UnitilyLang is designed around abstraction, immutability and composition. It's language agnostic, that is until we have to write logic."
Well, language agnostic, but apparently not domain agnostic or framework agnostic.
Then, there's
workflow DynamoDbUserStore
= 4 . (3 (2 1))
Pointfree and nameless. Anyone have any idea what it does? Does the subsequent picture help? Anyone think you could modify what it does without a lot of unpacking? I'll leave it to someone else to figure out what's going on with the "UnitilyLang complete project".Is Java verbose? Yes. Is it possible to be less verbose, especially if you invisibly provide domain knowledge "for free"? Yep. Is it possible to be too succinct?
I don't understand why this isn't the only (or at least the highest rated) comment in this discussion. There really is nothing more to say about this article. "4. (3 (2 1))" is not a program. "We apply the second dependency to the result of the first. We then Partially apply the 3rd dependency and compose the resulting function with the 4th dependency." is not an explanation -- what is the "3rd dependency"? Dropping names to somehow implicitly number something does save keystrokes, but it doesn't make for "good code" (the claim from the article's title).
A lot of the terseness of other languages is the result of dynamism (Ruby, Python) and so on which basically gives you two thirds of the infamous patterns that people complain about more or less for free, and for other static languages that are predominantly functional a lot of the boilerplate simply does not exist because the primary unit of computation is the function and that spares you a hell of a lot of complication.
It's the C++/Java class of languages that all suffer from the same verbosity and complexity and I don't think it's because the particular languages do anything wrong specifically but that the combination of paradigms simply leads to large, complicated structures.
If you could write your functions out of any class, you get 3 lines an an indent level less, which I think reduces the cognitive load. you could change your private fields to be by convention instead of forced just by prefixing m_varName or whatever...
I dislike java because I feel the language somehow doesn't trust the developer to know well the system he is coding. It is like handing a blunt knife to a chef expecting he doesn't know how to handle it, making his work annoying, but if he has to work for more time, he can bill more hours right?
Nobody - NOBODY! - used to write such Java code in the 2000s, at least the early ones. This insanity slowly started around 2005 and then really took off late 2000s and then particularly 2010s.
Again, thats not Java.
Edit: Example rewrite of UsernameLetterCountPublisher as a function since I realized a lot of people here might not be familiar with Java:
public static Supplier UsernameLetterCountPublisher(
final Supplier<Config> configProvider,
final Function<String, Config> userIdProvider,
final Function<User, String> userProvider,
final Function<String, User> userMessageCreator,
final Consumer<String> publisher) {
return () -> {
final Config config = configProvider.apply();
final String id = userIdProvider.apply(config);
final User user = userProvider.apply(id);
final String message = userMessageCreator.apply(user);
publisher.apply(message);
// I didn't bother create a new interface for this so reused supplier.
return null;
};
}ie A lot of the 'boiler plate' is stuff that allows the compiler to check for errors, and humans understand the code but at the same time allows you to vary behavior if you want.
Java also has language simplicity and consistency [1] ( few syntax short cuts ) - which helps humans understand code - the coding equivalent of 'plain english'.
Whether you see that as a good thing or not depends on whether you are writing self contained scripts or large long lived complex systems.
[1] the later bolted on generics, not so much.
No, the class name is not boilerplate. No, using parentheses rather than whitespace to indicate scope is not really cutting out boilerplate.
No, defining custom hashcode, equals and toString methods is not boilerplate or even always necessary, that's something you've chosen to do.
Much of the complaint about things like constructors and getters/setters can be resolved using something like lombok.
Honestly this looks like someone really labouring the truth in order to try to prove a point.
I feel like Java makes you feel like you're accomplishing a lot, but in reality if half your code is generated by an IDE, and is impossible to understand without the help of a bunch of expensive tools, I do have to wonder how good it actually is. I'm not sure I've ever seem a Java project where I didn't shortly after think "this probably would have been better with Clojure or something".
I know a lot of really smart people who absolutely love Java, so I'll admit that it's possible that I don't know what I'm talking about, but I'd like to think I'm reasonably competent at this coding thing now, and Java is routinely the worst part of my day at my current job.
To make it clear, I'm not knocking the JVM. That's a pretty cool piece of tech, and enables awesome stuff like Clojure and Eta.
I think a lot of people here, and probably out there in the world, have missed the direction java has taken in the last 5 years or so.
I'm not saying it's definite that you don't know what you're talking about, just that perhaps the changes to more functional styles, lambdas, function passing, stream processing etc might have passed you by. It's a much more expressive language now.
Or it might not, you may be bang up to date and still hold that opinion :)
The Optionals are definitely useful, but they're a far-cry from something like Scala's algebraic data types. The streams are cool, but compared to virtually any of the list-processing libraries for virtually any other JVM language, they're fairly primitive.
Being able to pass functions around is great, but the lambda syntax is pretty messy in Java IMO (the weird dichotomy between Function and Supplier still confuses me occasionally). Not to mention that generics in Java are still really weird and confusing...the dichotomy between the primitives and boxed types is strange, at least in regards to generics.
And even with all the improvements, we still have issues like the expression problem without much of a solution; the fact that I can't add to existing types or have something akin to a multimethod means that there ends up being a ton of wrapper classes and "extending but adding one method" classes all over the place, adding to the noise of files.
If I have any choice in the matter, since my team is a JVM shop (at least on the server), I typically choose Clojure. With Clojure I get much more concise and clear code, thread safety by default, an extremely fast and interactive development process, hot code reloading, and access to literally every Java library. I will admit that occasionally I would prefer to have a good type system (which Clojure sorely lacks...typed clojure isn't great IMO), but nine times out of ten, I am pretty happy with Clojure overall.
TL;DR: I should make it clear, and I realize that I didn't in my previous post, Java (the language) has improved; I definitely won't deny that. It's just not improved fast enough to address all my complaints. The language is still incredibly wordy and noisy, even with the improvements.
For example, the aws sdk for dynamo db contains DynamoDBMapper, which will convert dynamoDBItem to your desired class, so the entire DynamoDbUserProvider is totally unnecessary. It would simply be "dynamoDbMapper.load(key). This also removed the need for Config and DynamoDBTable classes.
Also, the entire boilerplate of toString hashCode equals can be easily avoided by using Lombok. I am sure some people will consider Lombok a hacky tool, but I don't know of practical issues with it. It integrates well with IntelliJ and Eclipse, and I think most java build tools.
Granted, with python you don't need a class to read from db, and can read into a dictionary, but you also won't get the niceities like auto completion if you do that, and will be much more vulnerable to typos.
The "90% boilerplate" in the title is highly misleading though. If you build an application that contains no business logic at all, then by definition, you're left with nothing but boilerplate. Especially if you insist on formalities like creating interfaces with only a single implementation.
In real projects that I've worked on, the vast majority of the code is application specific logic, with heavy use of external libraries to get rid of any generic logic. The Java boilerplate makes up a small portion of the overall code base, and has certainly never been a deal breaker.
So you still have those libraries and frameworks, and its a similar strategy to using Groovy, but presumably doesn't have the same performance penalty as it generates idiomatic Java code.
> using groovy is amazing, and can reduce your lines of code quite a bit
?
And that involves a lot of boilerplate code; in fact so much that the framework comes with a tool (ng generate) that generates the code for you. A single component is split in 4 files and needs to be explicitly wired up in another file (the module). An empty test case is more than 20 lines of code.
The point is. This is not really due to the language.
Angular is written in JavaScript which under other circumstances can be a very condensed language.
It is not really the language that mandates verboseness and boilerplate but the conventions, style, framework and so on.
Java can be low on boilerplating and a lot more condensed; though one has to look outside the big mainstream frameworks for that.
But, all the same, I want to play devil's advocate and argue that a lot of this mess is really about Java-the-culture moreso than Java the language.
You can use `public final` fields instead of private fields with getters but no setter. You can use public fields instead fo a private field plus a getter and setter, too. It'll save a lot of boilerplate, and, if you're doing it right, be functionally equivalent. The only things stopping you are cultural factors. First and foremost, it's taboo. That's, frankly, the usual reason. Second, some libraries that rely on reflection aren't equipped to handle fields. Not because they can't, but because the getter/setter pattern is so culturally entrenched that not following it is almost unthinkable. Finally, and this is the prototypical reason that motivated this idea in the first place, because, 20-odd years ago, some very clever people decided that it should be very easy to do Truly Obnoxious and Anti-Social Things like replacing a simple field access with a database round trip or some spooky-action-at-a-distance state mutation without having to tell any of your colleagues what you'd done.
To an approximation, a big motivation for the functional backlash against OOP is the realization that maybe we shouldn't be so quick to enable ourselves to do Truly Obnoxious and Anti-Social Things. Maybe it's even desirable that we not do that. Of course, politeness mandates that we couch that observation in gentler language using terms like "referential transparency."
This maybe speaks to another aspect of Java's culture: Perhaps due to its corporate roots, Java developers tend to rely strongly on thought leaders for advice. Regular book authors, conference speakers, bloggers, etc. are the arbiters of best practices. The more books, lectures, and blog posts one delivers, the more name recognition one has, the more trusted ones opinions become. In the limit case, you spend all your time telling people how to maintain code, and no time actually maintaining code.
Strip all that mental noise away, and, while it doesn't turn Java into my favorite programming language, it at least gets easier to see how Java doesn't have to be any more of a boilerplate-ridden mess than any other language of its generation.
Just don't try actually writing clean Java code at work. That's a path that can only lead to unemployment.
A lot of people are the opposite – they have an easy time filtering and skimming, and prefer all the details to be there on one page.
I think this plays a pretty big role in language choices – I find codebases in more concise languages much easier to parse & work with, but others say the same about Java/C#/etc.
If you onboard lombok, you get data classes, which resolves the property boilerplate. If you use IntelliJ the code is generated.
I used Java and Go and I have to admit, that Java feels like the more practical language.
Generics and error-handling are really painful in Go.
The most painful part in Java is having to deal with other people's code that makes it look weird. Google has a very good set of libraries that make the Java world much more pleasant.
Even big companies like Rational tried their best to generate boiler plate code from UML diagrams. Also BPMN incorporated findings from the early days.
But after nearly 30 years of programming experience I know one thing for sure: when it comes to real use cases beyond counting letters in a string only dedicated generators or very limited general ones continue to work. If it were the other way around there'd be product turning natural language directly into at least a single formal one.
And every (Java-) programmer who still repeatedly writes down lines of code doing the same boring input-processing-output did miss at least aspect orientation, Java agents and this is the most powerful of all: ANTLR. Runs right away with your BNF-grammar from Maven or a plain JDK. But be warned, this is like any other complex craftmanship, you need to start with cleaning the workshop before you can operate the heavy machinery.
That said, I like the motif brought up here, and I think the only thing lacking from the piece is how this stifles developers' engageability and creativity.
Have you considered that you might be doing it wrong?
I will say this for java though : it has what is in my experience the best IDE support. Kotlin is not that bad but all these generated sugar (e.g. in a data class) make some operations like "find usages" way more complex and slower.
Of course, Java needs some radical improvements, but people are doing that, and I can't wait for the day where these complaints will stop.
Verbosity is a personal choice too, and some people would prefer explicit configuration over convention, and that is a choice too.
Haskell is wonderful in its partial application, '.' composing, and other such tools, that really let you quickly and easily specify how your data flows through your functions. My gripe is that it starts getting weird when the order of arguments don't line up, when you have to swap some values, when you have to take a partial result and only apply it 3 steps later.. the Haskell syntax really breaks down from its usual purity in those cases. A flow diagram picture, similar to as shown in the article, makes it immediately obvious what is happening. I wish there was more integration with current IDE's for this sort of different representation styles
Firstly the Java patterns. Obviously it's not really necessary to use them for my toy example, but I don't want to write a blog about a full scale enterprise app. I have seen multiple tech teams at different companies evolve separately towards using similar patterns in both Java and C#. Doing so solves problems in concurrency and maintainability (This is documented in the other blogs on the Unitily site https://www.unitily.com/learning.html). I've seen teams be very successful using them. I like Java. Im not criticising it, I'm trying to explain why people do.
UnitilyLang doesn't exist, I'm not writing that language, the examples in that article are the only code snippets I've ever written (and probably ever will). The point is that there is a one to one mapping between that representation and the java code using the described patterns. This highlights the extra code you have to write in Java (and similar languages) just to support the clean code patterns.
Sure if you used Kotlin or Closure (or any functional language) it becomes less of an issue, and this is exactly the point I'm trying to make. However, its rare you have the option to choose a language, so developers end up coding in Java and complaining about it.
I don't like the new title the moderators have created, (perhaps the my initial one was a bit click baity) but generating code is only mentioned once in the last paragraph. The point of this article is to highlight that once you follow a set of coding principles which solve specific problems you are going to have to write extra code to do so. Perhaps something like
Writing clean code in Java requires boilerplate, learn to love it.
would have been a better title.
The video demo at the bottom of the article generates the code from pictures not UnitilyLang. These pictures are tightly coupled to the specific coding patterns, but is (in my opinion) a more natural definition than both Java and UnitilyLang.
Could you give us some background on your professional experience? There is no "About" section on the site.
https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpris...
It's just signposting, you can learn to look past it so soon it makes no difference.
I play music and don't need the treble clef on the stave to tell me what notes to play — you could call that boilerplate and get rid of it, but I don't think it would make it easier to play or read
> workflow UsernameLetterCountPublisher > = 5 (4 (3 (2 1)))
The workflow notation seems a little rough for readability even though its functional meaning fairly clear as you pretty much have to read a specific invocation of a workflow in context to confirm what's happening since the functions in a workflow could be anything and the workflow declaration is so generic.
On the other hand, when you see the named picture versions it's very clear. And I can't think of anything to address my minor criticism doesn't just make the notation worse.
Another alternative is Groovy
Honestly the only reason I would choose Kotlin over Java today is properties. Everything else is nice to have, but absolutely not important.
I would argue the null-checking is important.
Also, it's not well-integrated into the type system. Sure, you can get by with them, but it's certainly not ideal. It also requires someone to do work to set it up, instead of it just being the default.
One idea to get it actually usable: by default it shouldn't update any code you don't touch. (Some systems will load the file into a memory structure and then save it back ignoring the existing formatting, thereby making commit logs much harder to reas if you need to.)
Especially in orgs for which the code/tech is a secondary issue, only seen as a risk, they'd rather higher 5 more low-cost people to deal with the framework cruft than just hire the right people for the job.
Each concatenation will create a new string. That is not good.
If you find Java verbose, just use Scala or Kotlin.
[1] https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.htm...
I'm not a Java programmer but why would you actually need all the boilerplate for this? I'm under the impression that this should be doable in any language with just two lines of code: fetch and print.
The website has a front page, an applications page with 1 example, and a read the article page with a numerated bullet point list (which turn out to be hyperlinks, but I only accidentally noticed that when mousing over the text.)
That's it.
I'm wary of going too far in the direction of functional notation for the same use case though. Replacing code that is slow to read with code that is slow to expand in-head may not be the win people expect it to be.
How do I map the numbers back to their sources?
What the hell happened to the world.
I also wanted to add something I answered on a similar question in another less public forum a few days ago, but it turns out to be in another (human) language so I'll just add a summary:
> 1. Java IDEs are way more sophisticated than anything else except Visual Studio with ReSharper. Learn one of them. I disliked Java strongly until someone used a couple of minutes here and there to show me navigation and editing. (Hint for people who come from editors: Java IDEs understand the difference between the same letters i.e. word in different context. If you rename a variable in one method it won't touch other things that contains the same characters elsewhere in the file.)
2. Learn Maven and/or Gradle. You know you've got it right when everything works as expected and there's next to nothing left.
3. Don't trust everyone: people in this thread mention consultant Java. I was exposed to it early and it only took a few months to realize that certain (well paid I assume) consultants knew less than me: adding log statements and rebooting application servers every time they debugged (the correct way is of course to set a breakpoint and single step.) I also cleaned up a lot of their code, including a 6000 lines of XML implementation of a crude authorization framework that they had made because they didn't get Facelets.
...
"Hey look another tool that can generate a lot of boilerplate code you then have to deal with and makes the easy stuff easier and the hard stuff almost impossible!"
I think the right consideration is that they have simply done a poor job writing this program.