Hacker News new | past | comments | ask | show | jobs | submit login

My feelings exactly. Go was particularly disappointing, it promised everything but only felt like a sidegrade from Java. Screw it, a downgrade, until go errors get stack traces.



The reason I prefer the Go ecosystem to Java is cultural, rather than technical. Sure, the JVM is very impressive and the language has been evolving, but the culture around Java seems to encourage needless complexity.

Of all the languages I've had to work with trying to get to know unfamiliar code-bases, it's the Go codebases I've been quickest to grok, and yielded the fewest surprises since as the code I'm looking for is almost always where I expect it to be.


I think kotlin is what Java should have been like. The same capabilities but with less cumbersome constraints.


When I used Kotlin, it felt like Java, but with the antipatterns baked in as language features.


Could you explain which antipatterns you're referring to?


Leaning heavily into object orientation, including baking in things like companion objects, object expressions, etc. Encouraging utility classes that tack half-baked functionality onto existing types. Smart casts encouraging overly complex hierarchy.

While operator overloading and infix functions aren't a Java anti-pattern, I also think the language would be improved by their removal.


Even if you sincerely believe that OOP is an anti-pattern, surely it was firmly "baked into the language" in Java already? Kotlin is fundamentally a better Java, not a rebuttal to Java.


It is all but impossible to use Kotlin without an IDE telling you everything, and for that I find the language interesting. And for comparison, I did in fact write Java without an IDE for an extended period of time.


The don't embrace that culture. Embrace a simpler culture. Strive for simplicity. Push for fewer dependencies.

Simple example, JAX-RS running on top of Java SE. I agree, JAX-RS is not what one might call "simple". It IS complex, or I should say, it CAN be complex. But Happy Path, staying in the middle of the road, it's pretty sweet for knocking out HTTP backed services. The Jersey reference implementation will do most anything you need (including stuff not "included" in raw JAX-RS). No need for a container, no need for a lot that stuff and all that hanger-on. The core runtime is pretty broad and powerful.

Consider my current project, it uses the built in Java HTTP server. Which works! It's fast, it's functional, it's free. (Yes, it's in a com.sun.net... package, but it's not going anywhere.) It's awkward to use. It's aggravatingly raw. It follows the tenet "why be difficult, when, with just a little effort, you can be impossible."

So, I wrote a simple "servlet-esque-ish" inspired layer for response and request handling, a better path based regex-y router, and a nicer query parser for queries and forms. 500 lines. Add on a JSON library and I can process JSON-y web request/response, easily. (I also wrote my own Multipart processor -- that was another 500 lines, boy that was fun, but most folks don't need that.)

A little bit of code and the built in server is MUCH easier to use. No tomcat, no deploys, zip. ...and no dependencies (save the JSON library).

Something all of these cool frameworks and such have shown me is what's really nice to have, but at the same time, just what isn't really necessary to get work done. I mean, CDI is really neat. Very cool. But, whoo boy. So I have a single singleton to handle application life cycle and global services. It works great with tests. I have a 50 line Event Bus. I have a 100 line "Workflow Engine". 150 line java.util.Logger wrapper (which is mostly, you know, wrapper). I wrote that way back whenever they introduced varargs to java (Java 5? 6?). The modern Java logging landscape is just...oh boy. I'm content with JUL -- I can make it work.

My current project is "magic free". I think @Overide in the single annotation anywhere in it. But it's comfortable to use, the cognitive load is quite load (outside of the actual application itself, which is NOT low -- sheesh). No swearing at frameworks. It's all my fault :).

Anyway, the point is that "simple Java" lurks in there. It needs a bit of uplifting, but not a lot.


You've made your point, but note the built-in HTTPS server only supports TLS 1.2, so don't use that for production code. (For testing it's probably fine.)


TLS is supplied by the underlying JDK, which has been 1.3 for some time. How is HttpsServer not 1.3?


Not sure, but the API docs say 1.2. Maybe the com.sun packages include their own implementation of TLS, separate from the TLS used in the rest of the JRE.


You've made the project yours, nice! How many others work on it?

It's an uphill battle to convince my co-workers to do things my way.


True, but the culture around go isn't any better. In my experience go developers are former java developers so they have the same culture of thinking it's ok to ignore how a unix system works. So you will have awful logging, daemons that never report they're ready, badly handmade command line parsing and so on.


funny. java is known for its stacktraces where you need 3 vertical monitors stacked together to see the whole thing and it still doesn't tell you anything useful about why the app crashed.


I think that's a bit unfair. Generally the stacktraces tell you exactly what and where the problem is. Generally shouldn't be more than a dozen lines or so.

The main area they get excessively lengthy is in certain frameworks and testing tools that can add like 100 lines to the trace.


Compared to Go where I always have to remember to print the stack trace in every goroutine’s panic handler or use a custom error type that includes the stack trace or I get nothing? And I have to do this very basic thing for every service I spin up? This might not matter for pet projects or CLI applications, but it matters a lot in large scale mission critical servers.


Tip of the Day: You can wrap errors using "fmt.Errorf" in Go - this gives you a nice chain of error messages which is a pseudo stack trace. Another tip is to define and leverage sentinel errors when wrapping errors so you can test using "errors.Is" higher up when doing error handling in the call-hierarchy - like when mapping to http errors or exit codes.

Using "fmt.Errorf" is lean and painless compared to defining custom errors.


And be very careful in constructing your error messages so that grepping the error string does not return you 50 matches. It's not the same as getting the error message + file name + line number.

This is the whole story of Go, they pick something established and reimplement a heavily cut down version of it for "reasons", then slowly catch up to competition over the next decade or so.


It’s still not stack traces for every error, which is the default in every other language with stack unwinding.

In practice you have to use a combination of error wrapping and custom stack trace errors for your production logs to be useful on failure. The stdlib errors really should have stack traces.


> stacktraces where you need 3 vertical monitors stacked together

If you wrote code with such deep stacktraces, it's all on you.

There's a performance cost to all that excessive stack depth too, often.


This is unfortunately common across most ecosystems today. Such lengthy stack traces are routine in JS, and fairly common in C++.


as someone who had to deal with Java stack traces the entire week, I feel attacked.

God bless Gemini 2.5 Pro which ate all the traces for breakfast.




Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: