How To Use @Value Annotation With Project Lombok in Java Applications

@Value is frequently used when creating Immutable classes. Check here if you need a guide for defining immutable Objects.

@Value annotation is like an "all-in-one" special annotation that groups functionalities from multiple annotations namely:-

  1. It makes your fields private and final by default.
  2. Getters will be generated for the class. ie @Getter.
  3. An "all args constructor" will be generated for initializing non-intialized fields. ie AllArgsConstructor.
  4. It adds no setters. i.e @Setter(AccessLevel.NONE).
  5. The annotated class will itself be final by default.
  6. A toString() method will be generated for the class i.e @ToString.
  7. equals() and hashCode() methods will also be generated for the annotated class i.e @EqualsAndHashCode.

This is the case, because we would basically achieve the same by annotating our class with final @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) @Getter @EqualsAndHashCode @ToString @AllArgsConstructor

# Dev Environment:

  • Apache maven (v3.6.1)
  • maven-compiler-plugin (v3.8.1)
  • lombok (v1.18.8)
  • Apache Netbeans (v10.0)

TIP: For those who would like to follow along with this blog, just clone my repository git clone https://github.com/steven7mwesigwa/java-tutorials.git and navigate to java-tutorials\project-lombok\value. In there, you should be able to play around with all the source code used in this blog post.

# Demonstration : (Without Lombok)

Before we move on, let's look at how we would normally type out code for a regular Java immutable object without using any lombok specific annotations.

Let's create a Person class that will have a manual implementation of @Value annotation. Full code here


//demo1			
package com.stevenmwesigwa.value.demo1;

import java.util.Objects;

public final class Person {

    private final String firstname;
    private final String lastname;
    private final String ssn;

    public Person(final String firstname, final String lastname, final String ssn) {
        this.firstname = firstname;
        this.lastname = lastname;
        this.ssn = ssn;
    }

    public String getFirstname() {
        return this.firstname;
    }

    public String getLastname() {
        return this.lastname;
    }

    public String getSsn() {
        return this.ssn;
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 43 * hash + Objects.hashCode(this.firstname);
        hash = 43 * hash + Objects.hashCode(this.lastname);
        hash = 43 * hash + Objects.hashCode(this.ssn);
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Person other = (Person) obj;
        if (!Objects.equals(this.firstname, other.firstname)) {
            return false;
        }
        if (!Objects.equals(this.lastname, other.lastname)) {
            return false;
        }
        if (!Objects.equals(this.ssn, other.ssn)) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "Person{" + "firstname=" + firstname + ", lastname=" + lastname + ", ssn=" + ssn + '}';
    }

}

As you would notice, this is a lot of boilerplate code for just 3 fields in our immutable Person class.

It was actually even more painful for me to paste it here, but i had no choice but to show you how big your code can get.

For those who aren't familiar with what's going on, am basically providing:-

  • getters - To be able to return field values.
  • toString method - To return a more readable string representation of our object.
  • equals method - To check if 2 objects are equal.
  • hashCode method - Simply returns a hash code value for the object.
  • A constructor - A special constructor that takes one parameter for each final field with no initial value.
  • NOTE: Our class is final, our fields are private final and we have no setters defined.

Now, enough with the manual implementation. Let's try out some magic provided by Lombok specific annotations.

# Using @Value Annotation From Project Lombok Library : (Lombok)

The project Lombok authors decided to work on a quick solution to eliminate all this boilerplate code with just one annotation.

Adding @Value annotation right on top of your class informs Lombok to automatically generate all the above listed specifications for your immutable class.

Let's create a Person class, but this time making use of @Value annotation... Full code here


//demo2			
package com.stevenmwesigwa.value.demo2;

import lombok.Value;

@Value
public class Person {

    String firstname;
    String lastname;
    String ssn;
}

Imagine if i had told you that by annotating my class with @Value annotation, Lombok would generate all the functionalities we had manually implemented in our previous example.

You can now see how small our code looks. As they say, less code less bugs, this allows us to focus on more important parts of our logic.

# Make Lombok Generate A Private Constructor And A Public Static Factory Method For An Immutable Object.

There're cases when you don't want your client to have direct access to call your constructor.

For a case like this, You normally want to make the constructor private and provide a public static factory method wrapper to the client so that they're still able to set field values.

This can be achieved with @AllArgsConstructor(staticName="of") or @RequiredArgsConstructor(staticName="of") annotation depending on your use case.

This is cool and fine, though you probably should keep in mind that we're able to achieve the same result by passing a staticConstructor parameter to @Value annotation. i.e @Value(staticConstructor="of").

NOTE: You're free to provide your own "staticConstructor" value, though a lot of developers prefer to use "of".

Let's create a Person class to demonstrate this behaviour. Full code here


//demo3	
package com.stevenmwesigwa.value.demo3;

import lombok.Value;

@Value(staticConstructor="of")
public class Person {

    String firstname;
    String lastname;
    String ssn;
}

With the setup above, the client can easily create a new instance of the class like this Person.of(...)

When we create and run an App.java class that makes use of this Person class Full code here, we have successfully returned a string representation of our Person object. i.e Person(firstname=Steven, lastname=Mwesigwa, ssn=585-51-6704)

Lastly, You have the flexibility to change the default behaviour of @Value annotation by explicitly adding various lombok specific annotations i.e @NonFinal but you should be cautious about breaking the specifications that make an object truly immutable.

I hope you learn a thing or two from this blog. Thanks for checking it out.

If anything is unclear or you wish to make any corrections, don't hesitate to leave a comment below. Your feedback is greatly appreciated.

Sharing is caring. Share this blog to help out others getting started with 'project Lombok' library for Java applications.

Прощай!

About The Author   

Steven Mwesigwa

Software Engineer at Vogue Book Consultancy Services Ltd


Picture Of Steven Mwesigwa

Steve is currently a software developer at Vogue Book Consultancy Services Ltd and a technology author. He holds a Dip. in civil engineering from Kyambogo University. He founded and maintains stevenmwesigwa.com a website that receives more than 1.5 thousand visits per month. Steve can be reached on Twitter at @steven7mwwesigwa