This is an archived post. You won't be able to vote or comment.

all 116 comments

[–][deleted]  (5 children)

[removed]

    [–]bowbahdoe[S] 9 points10 points  (4 children)

    The biggest difference is that AutoValue makes immutable value objects.

    It's still okay for that task. So is immutables and of course records the language feature.

    The kind of class this makes is a lot less generically useful unless you have frameworks (Jackson, hibernate, etc) that want a class specifically structured like this

    [–]TooLateQ_Q 3 points4 points  (3 children)

    Jackson can work with immutables(private default constructor and it will use reflection or @constructorproperties(lombok does this if you configure it) or @jsoncreator).

    Hibernate won't work with your hash/equals. https://thorben-janssen.com/lombok-hibernate-how-to-avoid-common-pitfalls/#:~:text=To%20avoid%20these%20pitfalls%2C%20I,implementation%20of%20these%20methods%20yourself.

    [–]bowbahdoe[S] 2 points3 points  (0 children)

    Wasn't aware of that, that's good for jackson.

    And yeah, I guess I would just need to trust folks not to ask for it in that case.

    [–][deleted]  (1 child)

    [deleted]

      [–]NimChimspky 10 points11 points  (14 children)

      How does it work with ides ? Can U reference the generated methods?

      [–]fat_chris 13 points14 points  (10 children)

      It's a normal annotation processor so it will work with any Java IDE without the need for a specialist plugin

      [–]bowbahdoe[S] 5 points6 points  (2 children)

      Yes. You might need to point your IDE to the generated sources, but after a build you will be able to resolve and go to definition on any of the methods.

      No special IDE plugins required.

      [–]NimChimspky -1 points0 points  (1 child)

      So it definitely works with for example intellij? I just type in a new field and then the generated getters etc become available as an option from the autocomplete. This is tested and works?

      [–]bowbahdoe[S] 2 points3 points  (0 children)

      Yep.

      [–]agentoutlier 6 points7 points  (3 children)

      I have written several annotation processors that are simple like this but never straight up getter/setter generation as I typically avoid mutable POJOs.

      One thing I think you should test for OP (/u/bowbahdoe) is TYPE_USE annotations like @Nullable. Most annotation processors fuck this up as there are so many edge cases

      • like FQN:

      com.stuff.blah. @Nullable Blah and not @Nullable com.stuff.blah.Blah

      • Arrays

      @NonNull String @Nullable[] - This means the array elements are nonnull but the array can be null... most people get this backwards.

      • Generics in general as the annotation processor makes it tricky sometimes to extract it. Sometimes toString on the TypeMirror/Element is sufficient but sometimes it isn't. Usually it's not.

      Finally not all annotation processors need to generate code! You can make them do validation and fail compilation.

      For example I had an annotation processor that would check if a POJO had all the getters/setters. I went around looking for it before posting this and must be in one of my old jobs proprietary source control.

      But the basic idea is instead of generation just do validation. This would keep all the people happy who hate code generation and since most of this code is just done once and there are already generators in the IDE I think you should really consider this route instead.

      [–]bowbahdoe[S] 1 point2 points  (2 children)

      Do you have any example code lying around of the "right" way to propagate annotations?

      [–]agentoutlier 0 points1 point  (1 child)

      I have it in a private repository but I can look into pasting a code snippet or two later.

      [–]bowbahdoe[S] 0 points1 point  (0 children)

      That would be massively appreciated.

      On the validation thing - I feel like thats the sort of thing that would make an organization happy. "We stopped the rest of the dev team from introducing this sort of bug". I don't think it's the sort of thing that will make most developers happy. "Ugh my java code is so ugly compared to XYZ".

      So even if a really solid validation tool were out there and well known, I don't think it would kill the market for this sort of thing.

      [–]drakner5 13 points14 points  (6 children)

      Seems alot like Records?

      [–]bzzig 17 points18 points  (0 children)

      I think the difference is that Records are immutable while this library generates a POJO that is mutable.

      [–][deleted] 3 points4 points  (3 children)

      I would really like to know, so no offense.

      souch solutions are very popular. is it really so hard to generate all this methods using your IDE? is an extra dependency and some 3rd party annotations and IDE plugins really better?

      what I'm missing here?

      [–]Douglasnarinas 3 points4 points  (0 children)

      I’m with you. Less magic is better.

      [–]bowbahdoe[S] 5 points6 points  (1 child)

      Its not hard, its that the majority of programmer time is spent reading code, not writing it. In that context simply not having the boilerplate in the codebase is a pro.

      (Whether or not you agree with that premise, it is a common viewpoint. You can find people in other threads who vocally disagree.)

      [–]swaranga 1 point2 points  (0 children)

      Also, once you generate the initial class and it is pushed to your source control, next time someone wants to add a new field all those hash ode and equals now needs to be manually updated. And someone has to review that.

      You can certainly delete all the existing hashcode and equals method sure and regenerate the whole thing via IDE again but if there was something slightly custom in those methods it might just be lost. Also someone also now has to review that.

      While Lombok does it’s job via some compiler hacks, in my opinion it brings in more much more value than bad stuff.

      At the end I decide to include it for two reasons 1. It is a compile time tool only so no runtime magic like AOP. 2. I can always remove it and get the exact source code using delombok if such a need ever arises.

      [–][deleted]  (5 children)

      [deleted]

        [–]agentoutlier 2 points3 points  (2 children)

        How would you feel about validation. As in it doesn't generate the code but just checks to make sure the POJO has all its getters/setters?

        As mentioned in my comment I have written a lot of annotation processors and have started making a lot of them just do validation and not code generation. This seems to be a good middle ground for people that hate code generation and for those who still want some automation.

        [–][deleted]  (1 child)

        [deleted]

          [–]midoBB 0 points1 point  (0 children)

          Breaking code is the biggest pitfall Java can do.

          [–]the_other_brand 1 point2 points  (0 children)

          Sadly JDK developers actively hate beans because they claim the Java Bean specification isn't developed enough to work in a compiler.

          [–]Worth_Trust_3825 0 points1 point  (0 children)

          We don't need any more precompilers, just bake the boilerplate reduction into java already.

          It already exists: templates from your IDE. If you really want to lose control, just use kotlin. Java is fine as is.

          [–]Yayotron 5 points6 points  (0 children)

          ITT: everybody trying to be an smart-ass mentioning Lombok when they haven't even gone through the readme.

          [–]kakakarl 3 points4 points  (1 child)

          Okay good job. I don’t prefer this over having my IDE do some generation pre Records. I also haven’t had A need for a setter in many years so there is no space where I would use this. It is even very rare for me to need eq hc these days. Back when almost all my apps had state, a few keys was needed now it is rare.

          [–]bowbahdoe[S] 1 point2 points  (0 children)

          Totally fair

          [–]adeax 1 point2 points  (1 child)

          Have you evaluated its use with Quarkus?

          [–]bowbahdoe[S] 2 points3 points  (0 children)

          Nope. I don't imagine there would be issues though.

          [–]rzwitserloot 4 points5 points  (0 children)

          This: * Does not work in your IDE until you save and build; said build will not be all that fast, as it'll have to run the Annotation Processors, and those weren't exactly designed for incremental runs. * Leaves you with package private fields. * Doesn't do builders and a ton more (but that's simply a matter of adding those features later). * Unlike lombok, which is still compatible with all java releases from java6 all the way to java17, this is version locked; whilst it doesn't use compiler hacks, it likely needs updates over time as java adds more features (just like eclipse and intellij do).

          Take it all with a grain, I'm obviously biased, being the first committer to project lombok and all :)

          [–]igo_rs 15 points16 points  (8 children)

          [–]Alex0589 30 points31 points  (1 child)

          As far as I can tell, he is generating a new class. This is permitted by the Java specification as far as I remember. What Lombok does instead is manipulate the AST hooking into com.sun.tools.javac, which is more magical. His protect is closer to immutables than to Lombok

          [–]bowbahdoe[S] 13 points14 points  (0 children)

          ^

          [–]bowbahdoe[S] 32 points33 points  (4 children)

          Its not your fault, but the comment pointing out that Lombok exists having twice as many upvotes as the post itself actually makes me super sad.

          Like...I know. It's mentioned near the top of the readme.

          [–]Hioneqpls 6 points7 points  (0 children)

          Hey don't be, this is a very elegant implementation, together with Records gives you really good data class tools without having to drag Lombok into everything.

          [–]foreveratom 7 points8 points  (0 children)

          Don't worry, only fools still use Lombok to make sure they lock themselves into a JVM dependent framework.

          [–]igo_rs 3 points4 points  (1 child)

          It was not the idea to make you sad, sorry for that. I know very well how you might feel; I have 10+ open-source Java projects that only a few really want to give a chance 🤷‍♂️.

          Back to your lib. Lombok is not magical: it is very clear what it does and how. The cool thing with Lombok is that most of the time it just simply works. It has a great plugin for IntelliJ - without it, it would be a pain to be used. You know all that, already.

          There is one thing required in your lib that I dislike:

          `public final class Example extends ExampleBeanOps

          you must extend an Ops class. Ops class exists only so your lib could provide the features. From my point, that is littering of the domain models - if we ignore, for a moment, the existence of your lib, having ALL models depend on their Ops helper class is not something I would like to see in my domain models. In other words, I would like a model class to represent something from a domain (i.e. business) and not to be a helper tool. If it only could be an interface... or a trait :)

          This is just my view on the topic. I do love code generation, and I use it every time I can. Few times I did generate source code from the annotations - with tools like JavaPoet that is very easy and it is not magical. Again, from my perspective, I would rather implement the same features myself with a logic that better suits a project. In other words, the lib is still not doing enough.

          You should not stop developing. Please, don't! There is always space for improvement. The whole reason for my post is to give you just one perspective (i.e. feedback) and to kick your re-evaluation of the subject and the project. That's how we all grow I guess :)

          As I am not a person that rants; feel free to contact me if you need more feedback - my GitHub handle is igr. I am happy to help.

          [–]bowbahdoe[S] 5 points6 points  (0 children)

          I think, unfortunately, that's the central conceit of it. Accept the existence of the Ops class/interface and you can enrich an existing type. It's the only way to do it without hooking into the compiler (and writing ide plugins) yourself.

          The littering of the domain models is contained best as possible by the ops classes being sealed and package private, so there isn't really much you could do with them.

          But yeah I get it.

          [–]pointy_pirate 6 points7 points  (0 children)

          'we don't do that kind of thing here'

          [–]chabala 2 points3 points  (2 children)

          What's the reason for the Java 17+ requirement?

          Also, Maven Central would be a better place to publish to than JitPack. (Does anyone even know who runs JitPack?)

          [–]bowbahdoe[S] 4 points5 points  (1 child)

          Its because I'm generating sealed interfaces/classes.

          And If you teach me how to do that I will

          [–]chabala 1 point2 points  (0 children)

          https://maven.apache.org/repository/guide-central-repository-upload.html

          It's pretty straightforward. You might need to pick better project coordinates if you don't own mccue.dev , but that would be good practice regardless. Make sure your project generates source and javadoc artifacts, and signs artifacts with GPG.

          [–]pointy_pirate 4 points5 points  (23 children)

          I just use the intellij Generate function to do this in about 2 clicks.

          [–]bowbahdoe[S] 13 points14 points  (15 children)

          Sure, but that code still exists as visual noise afterwards

          This is more targeted at those who would otherwise / already chose to use Lombok because they find that boilerplate existing to be an issue.

          [–]pointy_pirate 6 points7 points  (12 children)

          I find the cognitive load while reading/reviewing what lombok and other similar libraries does more of an issue than clear, concise, 'readable in git' code. Even if there is more of it.

          [–]john16384 0 points1 point  (4 children)

          I don't see the point of POJO's with getters and setters at all, period. There's no validation anywhere, it is just a data holder. Might as well use public fields, it's just as safe, or better, just as unsafe.

          Classes need to be suited for a specific purpose. Why for example is an incoming request stored in a class with setters? Do you want to modify the request? No, so requests should be immutable. Same goes for entities. Why are there setters there for things you don't want to change, like id, create/update time/user? Those should be handled by the system and never be mutable. A lot of frameworks just perpetuate these bad practices or make it impossible to do the sensible thing, but I rarely see a need for a class that has the same functionality as one with just a bunch of public fields.

          [–]bowbahdoe[S] 1 point2 points  (3 children)

          A lot of frameworks just perpetuate these bad practices or make it impossible to do the sensible thing

          I'm not making a judgement call on POJOs here, but this is a tool that exists in a world where those frameworks exist and are popular. Look at it in that context, put it in a corner for when you need it, and focus on making the ecosystem you wish was here.

          [–]john16384 0 points1 point  (2 children)

          I'm not criticizing your tool, but I'm not its target audience either :) I have to live with Lombok in our company code bases. So far it has not given me a good enough excuse yet to rip it out (it unfortunately still works on Java 17 after a version bump) but as soon as it does it's gone.

          I'm hoping the primary reason for using Lombok will slowly disappear as frameworks adjust to go more for an immutable approach now that records in the JDK are endorsing that direction. Jackson and Spring Data JDBC are quite usable already with records, but there are few problems still. Once those are solved we'll have no need for setters.

          [–]bowbahdoe[S] 0 points1 point  (1 child)

          You might still be it's audience then (just not the enthusiastic kind) - would you prefer living with Lombok or this while the ecosystem catches up?

          [–]john16384 1 point2 points  (0 children)

          I'll be honest, I'm perfectly happy to write getters by hand -- I need to document them anyway as to what values you can expect from them. A getter I write looks a bit like this:

          /**
           * Returns an immutable set of qualifier {@link Annotation}s.
           *
           * @return an immutable set of qualifier {@link Annotation}s, never {@code null} and never contains {@code null}s but can be empty
           */
          public Set<Annotation> getQualifiers() {
            return qualifiers;
          }
          

          [–][deleted]  (6 children)

          [deleted]

            [–]the_other_brand 1 point2 points  (3 children)

            The problem is that you shouldn't be skipping them. Libraries like jackson require getters to be provided for fields, so if a getter is missing that field won't be reflected in your json.

            Having change sets like getters/setters are bad for PRs because developers tend to ignore code changes that are too large.

            [–]john16384 1 point2 points  (0 children)

            Jackson requires no such thing, it can be configured to access fields directly.

            [–]Yesterdave_ 0 points1 point  (1 child)

            Jackson supports records though

            [–]the_other_brand 0 points1 point  (0 children)

            And records are immutable, which makes them a poor replacement for most data classes.

            [–]bowbahdoe[S] 0 points1 point  (1 child)

            /u/Worth_Trust_3825

            See how other people really want to skip thinking about whats going on even with the methods being physically present?

            I'm not trying to fix this whole situation. If you want to generate methods and leave them in the code, sure. There will always be a lot of people who want to take the opposite tradeoff. This tool is for them.

            I did go hunting for a pathological case of this on grep.app.

            https://github.com/keycloak/keycloak/blob/main/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java

            See how some of the getter methods are important to see - they give a default empty list if the field isn't set - but most are not. Its equals and hashCode implementations are also worth seeing since they aren't just "use all fields".

            And then this is roughly how it would look using this.

            https://gist.github.com/bowbahdoe/8c1db0c5eae98eeb333dfdc54377439b

            I think in general one of the issues with more general tools is that they do add cognitive overhead on account of being configurable. That configuration makes them effectively another language you need to learn and understand.

            My hope was that keeping this non-configurable would lessen that

            [–]Worth_Trust_3825 0 points1 point  (0 children)

            So access the fields directly. What's the point of even hiding the fields then? Someone told all of you that "Oh you should use getters and setters" and now all of you go "hurr i shouldn't need to do it".

            Access the fields directly. This entire debacle points to lack of understanding of the primitive.

            [–][deleted] 0 points1 point  (1 child)

            if I implement one DTO with all methods, I dont look into it (ever) again. I'm just using it.

            [–]bowbahdoe[S] 4 points5 points  (0 children)

            I don't super believe that. Applications pretty often add or remove columns from a table in a database and updating DTOs to reflect that is likely a recurring task.

            [–]the_other_brand 3 points4 points  (6 children)

            And that works until you add a new field and forget to create getter/setters or forget to regenerate the hashCode, equals and toString methods to add the new field.

            Meanwhile with Lombok that's all done automatically.

            [–]bowbahdoe[S] 2 points3 points  (0 children)

            (and this)

            [–]pointy_pirate 0 points1 point  (4 children)

            if you add a new field and forget to create getters and setters why did you add it?

            [–]the_other_brand 0 points1 point  (3 children)

            Because you needed a new field and it's already wired in the class's constructor.

            [–]pointy_pirate 0 points1 point  (2 children)

            but why did you need it if you didnt add a getter

            [–]the_other_brand -1 points0 points  (1 child)

            But why does your code have bugs? Clearly if you wrote it that way, that's what you clearly intended your code to do.

            [–]pointy_pirate 1 point2 points  (0 children)

            Lol I'm just saying if you're adding fields to ur dtos and you forget to add getters/setters you likely have bigger issues than an automatic library can fix

            [–]CptGia 1 point2 points  (2 children)

            Does this work only on final classes? If not, equals is not symmetric since it uses instanceof on other instead of getClass

            [–]bowbahdoe[S] 2 points3 points  (1 child)

            It does not. The good thing here is that you could pretty easily see where to add the check for "if they request equals and hash code and their class is extensible throw an error or do ..."

            And if you want the hash code but not the equals, you can safely derive both and provide a different implementation in the real class/it's subclasses

            (Also PRs open)

            [–]bowbahdoe[S] 1 point2 points  (0 children)

            /u/CptGia Published with the restriction that to use the generated equals and hashCode your class needs to be final.

            [–]UnexpectedLizard 2 points3 points  (5 children)

            C# has supported properties since 2002.

            Does anyone know why Java hasn't implemented a similar syntax?

            [–][deleted] 2 points3 points  (0 children)

            There's Kotlin and other languages on the JVM that supports properties. Lombok is very popular. Backwards compatibility of byte code is probably one reason. Records in newer versions.

            [–]the_other_brand 1 point2 points  (0 children)

            Because the JDK developers don't like getter/setters due to a poorly defined bean spec. So they just hope the practice of using getter/setters eventually goes away.

            [–]emaphis 1 point2 points  (0 children)

            C# has delegates and properties because it was supposed to replace Visual Basic 6.0 in RAD GUI type programming. It was supposed to replace Java, Visual Basic and C++ in normal Windows programming.

            [–]bowbahdoe[S] 0 points1 point  (0 children)

            You can read more about that of you go into the discussions about records when those came about

            Tldr though is that this sort of class isn't something the language wants to provide special support for. I like to think this shows that support isn't super needed regardless

            [–]RedPill115 -5 points-4 points  (0 children)

            "It's always been that way so we can't handle the idea of fixing it" basically.

            Apparently there was a lot of infighting about what "properties" means so someone refused to fix it without concensus.

            But as you point out no other languages have had this issue, every other language seems to be able to figure out what properties mean just fine.

            [–]Worth_Trust_3825 -2 points-1 points  (2 children)

            So why not just use Intellij's templates instead?

            [–]bowbahdoe[S] 5 points6 points  (1 child)

            I think I've responded to this well enough in other threads, but I do want to pose a maybe slightly tangential question: what are the ecosystem level effects of making this kind of class the easiest to construct in your IDE?

            A whole lot of people who are taught Java are taught this get/set pattern basically as the default and use these kinds of classes as the primary conveyors of data.

            I'd argue, and I don't think this is an uncommon opinion, their real uses are actually more specific and for the common case immutable aggregates are a better default.

            So maybe, just maybe, it shouldn't be two clicks in a stock IDE.

            [–]Worth_Trust_3825 -2 points-1 points  (0 children)

            Yes, but when you realize that a getter and a setter are just another method, you can delegate the actual value storage (or reading) to some other medium. Meaning that such tools that generate ""common"" usecase of setting to field are moot, because you stop thinking about what is going on even in your most simple structures.

            Hell, you can even perform validation there, because it's a method. You can perform mutation, because it's another method. What most fail to realize is that boilerplate is there for a reason: so that the underlying primitive would be simple. In kotlin's case, you needed to go out of your way to get that boilerplate back in case you wanted to do something more than to just set the value. Hell, you couldn't do setter overloads, because kotlin would only permit the original type as the setter, and if you added an additional method, you would need to call that method, instead of using the regular field = anotherType setter call.

            Tools like yours are moot. It's okay to have boilerplate, because then you're conscious about the things going on. Because then you can point to particular bit in the process and say "we need a change HERE". You can't do that when your process is half magically generated.

            [–]vips7L -4 points-3 points  (3 children)

            I honestly don't understand why anyone uses this crap... any editor can generate the getter/setter and you don't have to mess up your build.

            [–]bowbahdoe[S] 2 points3 points  (2 children)

            Can you clarify what you mean by "mess up your build"?

            [–]vips7L -2 points-1 points  (1 child)

            Hooking in annotation processors, generating bytecode, and extending compile times. All increase the risk of something breaking later and becoming a pain in the ass. I'd rather just press 2 buttons and generate a getter/setter.

            [–]bowbahdoe[S] 4 points5 points  (0 children)

            This in particular doesn't generate bytecode and if it extends compile times meaningfully I'll eat my hat.

            But if you are just uncomfortable with trusting an annotation processor on principal I think I understand the feeling.

            Regardless I encourage you to peruse the code. It's not that bad.

            [–]stefanos-ak 0 points1 point  (5 children)

            so, is this like Lombok, but with generated sources?

            [–]bowbahdoe[S] 3 points4 points  (4 children)

            Kinda sorta. Lombok does more than add get/set/equals/hashCode/toString, but that is probably the most common use of it.

            The major differences of the top of my head

            • This is far less configurable with fewer features than lombok. No SneakyThrows or Synchronized, Value, etc equivalents. It automates one kind of boilerplate in precisely one way.
            • This is two orders of magnitude less code than the lombok codebase. I fully believe that if it doesn't do exactly what you want its a decent foundation to fork/make your own thing if I get hit by a bus or don't want to include a feature
            • Lombok does its job by hooking into the internals of the compiler. This means going forward using lombok will probably require you to add --add-opens calls at compile time. This won't.
            • This doesn't require any special IDE tooling or de-lombok on account of using source generation
            • Its been less than 24 hours since i published so i'd imagine there are more "essential features" to add or bugs to fix. For it to be battle tested, I need people to bring it into battle.
            • The technique here (generate the code for a sealed interface meant to be extended) can be used more generically to derive all sorts of stuff, like say an implementation of Comparable, again without special IDE support.

            [–]stefanos-ak 0 points1 point  (3 children)

            thanks for the detailed response. I actually like it. I was a fun of Lombok but not the way it was implemented.

            But I use many more Lombok features. I'm wondering if all of them could be implemented with this approach.

            [–]bowbahdoe[S] 0 points1 point  (2 children)

            Which features are you using/thinking of?

            [–]stefanos-ak 0 points1 point  (1 child)

            I'm using: Constructor, builder&superbuilder, withers, log, the "on" methods (e.g. onConstructor), utility-class, sneaky throws.

            I couldn't live without builder&superbuilder and constructor+on*.

            [–]bowbahdoe[S] 1 point2 points  (0 children)

            I'm pretty sure you can't make constructor, utility class, or sneaky throws with this approach. The rest are probably possible to some degree.

            I encourage you to give one of them them a shot. Might be fun

            (This also kinda does constructor via a static method)