top of page

Leveraging Project Lombok to reduce java boilerplate code for efficient model development in AnyLogic

Writer: Selaelo KgoaleSelaelo Kgoale

Updated: Feb 18

This is a special guest blog post by our new intern Selaelo Kgoale. Selaelo is still a junior in the simulation modeling world, and his fast grasp of the use of tools like Lombok impressed us so much that we asked him to write about what he had learned.


This post was made possible by the support of our Patrons. To become a member, click here 

 

As a newbie when it comes to coding I often find myself writing repetitive code to manipulate different classes and their content (i.e., variables and parameters). I know that this can be time-consuming and results in a code base that is bloated and messy, with an increased risk of potential syntax errors. As I am starting out my simulation journey I often dread the mere thought of writing all that code and wonder if there’s a way around this.


Luckily, there is!

You don't need to write the same code snippets over and over again.

Although the concept is not entirely new, as other AnyLogic modelers, like Felipe Haro from Noorjax have addressed the issue in a video before, we aim to also showcase some modifications and a specific feature of the Lombok library, the Builder annotation. This feature helps to streamline model building and reduce the code base as model complexity grows and the volume of boilerplate code increases.


Why do models become bloated the bigger they get and the longer we work on them?

  • As model complexity increases during development, we lose track of functions/methods that perform actions on variables and parameters.

    • To combat this, we leverage the OOP (object-oriented programming) paradigm. We set the access modifier of variables to private, limiting their manipulation to methods within the class/agent, and calling said methods to interact with variables from outside the class as best practice.

    • But this leads to a significant amount of boilerplate code (e.g. Getters and Setters), which bloats your code base and makes it harder to search through your code for the unique and custom code snippets that you will often be working on.

  • We will also naturally create more constructors for our classes to enable us to create specific instances of Java objects.

  • We also create complex toString functions to help make sense of our logging or traceln() functions.

All of the reasons above can lead to complex, bloated models with lots (and lots) of code...


Now, let's get the ball rolling with an example:


Note: The term agent and class are used interchangeably in this text.


Rather watch the video? Click here


The situation

We created a very simple fleet management model comprised of a truck agent that serves as a template for our fleet objects.


As shown in the figure below, the truck agent comprises a fuel Level variable, whose access is set to "private", its setter ('setFuelLevel') and getter ('getFuelLevel').


Note: If you are curious about how I managed to get the properties for three different objects displayed at once in AnyLogic - check out this video


The complication

Now suppose we want to incorporate additional variables and parameters to represent various truck attributes in our model better. For instance, we want to incorporate engine temperature last refuel date as variables and maybe, truck model, brand and color as parameters. This implies that we define more parameters and more variables when creating an instance of the agent.



Now, the challenge lies in the volume of boilerplate code (i.e. getters, setters and instantiating code) and the time required to implement all these new variables and parameters...


How can we simplify this whole process?


The solution

The approach to streamline the process is through the use of a Java library and annotations to automate and reduce the boilerplate code of a class such as our truck agent. 


The Library to be used is Lombok, visit the website here to download the library and learn about its features. Then, follow the instructions in this Stack Overflow post to import it into Anylogic.

Also note that for earlier versions of Anylogic, one must enable Library developer mode to leverage the functionalities, while the latest versions have it enabled by default.

Enabling Library developer mode

  1. Navigate to preference


  2. Enable Library developer mode

Now that we’re all on the same page, we can leverage the library's functionalities through annotations in the advanced option under properties of variables and functions called "Custom Modifiers." This is where we call our annotations to create the boilerplate code during runtime.


We can leverage @Getter and @Setter annotations, as demonstrated by Noorjax Consulting in the tutorial video here.

(Note that in their video, they opted not to use the visual representations of the variables but rather create variables using code in the "Additional Class Code" section in the agent properties.)


In our example, we are showing how you can add the annotations in the Custom Modifiers section and then automatically get the Getters and Setters created for you. See the screenshot below.



Do we need @Getter and @Setter for the parameter objects?


No - only for variables. But we can still add them if we want to.


Note that AnyLogic defines Getter and Setter methods for parameters.


If you call the agent you will be able to set the parameter of an agent using the automatic code that gets generated for you. For example:

agent.set_brand("Volvo")

This code will set the value of the parameter called 'brand' to '"Volvo'.


There’s also a feature for getters that calls parameters based on their assigned name as follows:

agent.getParameter("brand")

This code will get the parameter named "brand " from our 'agent' class.


Below is an example of how you might use code in this way.



This use of annotations eliminates the need to explicitly define getters and setters for variables using the function object from the pallet. We can confirm the functionality of annotations by calling getters and setters of previously and newly created variables (without manually creating functions) outside the class. This is fine; however, we still have an increased volume of code when creating an instance of our agent. This will increase as we define and add more attributes to our class


 

Advanced use of Project Lombok: Java classes


The most powerful use of project Lombok comes into play when you are using Java classes in your model. If you do not use Java classes in your model, it can possibly be one of 3 reasons

  • You like to struggle ;-)

  • You are either not building large or complex enough models

  • You do not know of the benefits of using Java classes and keep on using agents.


Either way, read this blog post to learn of the benefits of using Java classes and AnyLogic agents. - https://www.theanylogicmodeler.com/post/why-use-java-classes-in-anylogic

Luckily, AnyLogic is flexible enough for you to use either approach.

Recording trips using Vanilla Java

In our demonstration model, we have now added a Java class named 'TripDetails' to store information related to trips. Each Truck agent can store multiple trip records. The architecture of the model is now as follows:


  • TripDetails Class – This class comprises startTime, endTime, startLocation, and endLocation variables, parameterized constructors for initialization and getter and setter functions for respective variables.

  • Truck Agent – This agent includes a collection object (an ArrayList) called 'TripRecord' and the functions 'addTrip' & ‘printTrips’


We created the TripDetails class with the respective variables set to private access as a best practice. But now we also need to do couple things in order to make. theclass usefull for our model:


  1. Create fully and partially parameterized constructors

    The fully parameterized constructor is commonly used to create instances of a class where all the parameters are defined upon creation, while a partially parameterized constructor is ideal for use cases where some parameters are defined while others are using default values.


  2. We then create getter and setter functions for the private variables


  3. We edit the toString function to format the output printed on the console as follows.



Already upon the creation of the class, we can see how bloated the code is becoming... and how cumbersome it is to create the code...


Creating the java class and all the relevant functions/methods seems to be a tedious process with a large codebase, with that comes aforementioned drawbacks and a repetitive task when more variables are defined for the class .

For the creation of our current class we did the following:

  • Created 4 getters

  • Created 4 setters

  • Created two constructors

  • Created a to string function



I get tired just looking at it. And I remember how often I would start copying and pasting code and then make spelling and syntax mistakes along the way....


Note: In modern IDEs like Eclipse you have special functions to create the boilerplate code for you, like constructors and getters and setters.



You can even ask Chat GPT to create the code for you, and then you just copy and paste... :

You can check out the link to my ChatGPT example here


This will save you time.. but your vote will still be bloated and hard to go through...


So what now...Is there a better way?



Project Lombok to the rescue!


How can we leverage Project Lombok to expedite the example model development and reduce boilerplate code?

We can leverage the following annotations: (Click on each to learn more about them on the Project Lombok website)

We implement them as follows:

  1. After ensuring. you have Project Lombok correctly setup for AnyLogic and your model import the project library with `import Lombok.*;` in your Java class

  2. Apply the @Getter and @Setter annotations on the variables, The annotations create getter and setter functions for variables without the boilerplate code. We can now call the getters and setters of the variables on any class instance and remove the related boilerplate code.

    1. Our class already looks simpler!




    1. The @NoArgsConstructor and @AllArgConstructor annotations create a default constructor with no parameters and a constructor with parameters for all variables, respectively. We only need to add the annotations above the class declaration


      We can now have two versions of constructors when instantiating without the boiler code.



      b) The @RequiredConstructor annotation creates a constructor consisting of parameters for non-initialized final variables and variables marked with the @NonNull annotation. The previous @NoArgsConstructor annotation cannot be applied to a class with non-initialized final variables, so we will comment it for this demonstration. We set one variable (“endLocation”) to a final and annotate another (“startLocation”) with @NonNull. Now both must be in the constructor as the other are optional.



      We now have a two-parameter constructor option with arguments “endLocation” and “startLocation” when instantiating.




  3. Use the @ToString

    1. Now we can also remove the toString function as a new one has automatically been created for use for each of our fields in the Java class



  4. You can also use @Data

    1. As described by Felipe in his video you can also use the @Data annotation. It is a shortcut for @ToString, @EqualsAndHashCode, @Getter on all fields, @Setter on all non-final fields, and @RequiredArgsConstructor



      Overall these annotations significantly reduce the constructor-related boiler code and allow the creation of different constructor configurations based on the use case. This is good; however, we still have the challenge of readability when instantiating. It becomes hard to keep track of parameters as the number of variables increases within the class during development. How do we address this?


      The answer: @Builder




The @Builder annotation produces complex builder APIs to produce code required by the class to be instantiable automatically. For the scope of our tutorial, we will not cover the underlying code from the annotation. You can learn about that and more about the annotation here.


  1. @Builder is applied as follows:

    We apply @Builder to the class and remove the constructor-related annotations previously mentioned since they’re in conflict due to their underlying code performing somewhat similar functionalities.


We then create a local variable of type TripDetails named trip1 and, assign the instantiated class to it and add to the Trip records collection as an action of the “Add Trip Builder” button on main.

As you can see, with the builder API, you do not need to wonder what each argument in your constructor is changing, as you can see this explicitly from the code. Making your life so much easier.


Conclusion

Based on the demonstration, the Lombok library effectively streamlines the model development process. As we can see from the side-by-side comparison of the codebase of the class


It is hard to believe that these two classes do almost the same - in fact the one on the right does considerably more. To manually create the builder code is quite a nightmare in Vanilla Java, especially if you get to collections as variables.



The benefits of Getters, Setters and ToString are fairly obvious, but often it is not clear when to use which one of the annotations provides some way of constructing a new instance of a class. Each annotation has its properties, benefits, and use case. The table below contains the summary

Feature

@Builder

@AllArgsConstructor

@RequiredArgsConstructor

Object Creation Style

Fluent API (.builder())

Traditional Constructor (new)

Minimal Constructor (final or @NonNull fields)

Parameter Order Enforcement

Order is flexible

Order must be followed exactly

Only required fields must be set

Readability & Maintainability

Easier for many parameters

Less readable for large constructors

Less verbose, but requires an understanding of @NonNull

Usage in AnyLogic

Great for POJOs but not recommended for agents

Best for AnyLogic agent constructors

Best for agents with optional fields

Boilerplate Reduction

High (reduces builder logic)

Medium (removes explicit constructor)

Medium (removes only needed constructor)

Custom Defaults

Yes (you can set defaults)

No (values must be provided at instantiation)

Yes (only final or @NonNull required)

Best Use Case

Complex data objects like configuration objects

Entities requiring all fields at instantiation

Minimal object setup with required fields only


Resources

Watch the video by Jaco-Ben Vosloo below:


You can download the example model below or from AnyLogic Cloud here.



In the model, you can play around with some of the buttons and a few more we did not discuss in the blog post.



Thank you for reading and keep modeling!


Selaelo Kgoale


 

Selaelo Kgoale is a simulation enthusiast, new intern at The AnyLogic Modeler. Contact him on LinkedIn to get in touch.


What next?

If you liked this post, you could read more posts by following the links above. Why not subscribe to our blog or follow us on any of the social media accounts for future updates? The links are in the Menu bar at the top, or the footer at the bottom. You can also join the mobile app here!


If you really want to make a difference in supporting us please consider joining our Patreon community here 


If you want to contact us for some advice, maybe a potential partnership or project or just to say "Hi!", feel free to get in touch here, and we will get back to you soon!




 
 
 

Comments


  • LinkedIn
  • Facebook
  • YouTube
  • Twitter
  • Instagram

©2021 by The AnyLogic Modeler

bottom of page