Top 10 Best Bash Profile Configurations for Enhanced Productivity

When it comes to optimizing your workflow as a developer, having a well-configured .bash_profile can make a world of difference. Bash profiles allow you to customize your terminal environment, streamline your commands, and enhance productivity. Here are the top 10 best bash profile configurations that can help you work more efficiently:

1. Custom Aliases

Aliases are shortcuts for longer commands, saving you keystrokes and time. Here are some useful ones:

# Update system
alias update='sudo apt-get update && sudo apt-get upgrade'

# Clear terminal
alias cls='clear'

# List directory contents with details
alias ll='ls -alF'

2. Environment Variables

Setting environment variables in your .bash_profile can simplify command-line tasks and scripts. Common ones include:

# Java Home
export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64

# Add custom bin to PATH
export PATH=$PATH:$HOME/bin

3. Prompt Customization

A customized prompt can provide valuable information at a glance. Here’s a colorful example:

# Colorful prompt with current directory
PS1='\[\033[01;32m\]\u@\h \[\033[01;34m\]\w \$\[\033[00m\] '

4. Command History Settings

Fine-tuning your command history settings can improve efficiency:

# Unlimited history file size
export HISTFILESIZE=

# Append to history file, don't overwrite
shopt -s histappend

# Ignore duplicate entries
export HISTCONTROL=ignoredups

5. Enhanced Navigation

Simplify navigation with custom functions and shortcuts:

# Go up N directories
up() {
  local d=""
  limit=$1
  for ((i=1 ; i <= limit ; i++))
  do
    d=$d/..
  done
  d=$(echo $d | sed 's/^\///')
  if [ -z "$d" ]; then
    d=..
  fi
  cd $d
}
alias ..='cd ..'
alias ...='cd ../..'

6. Git Integration

Integrate Git into your prompt and define useful Git aliases:

# Show Git branch in prompt
parse_git_branch() {
  git branch 2>/dev/null | grep '\*' | sed 's/* //'
}
PS1='\u@\h \w$(parse_git_branch)\$ '

# Git aliases
alias gs='git status'
alias gp='git pull'
alias gc='git commit -m'

7. SSH Shortcuts

Save time on SSH connections by defining shortcuts:

# SSH to favorite server
alias sshmyserver='ssh user@myserver.com'

# Use specific key for SSH
alias sshwithkey='ssh -i ~/.ssh/my_key user@myserver.com'

8. Custom Functions

Define custom functions for repetitive tasks:

# Create a new directory and navigate into it
mkcd() {
  mkdir -p "$1"
  cd "$1"
}

# Search for a pattern in files
search() {
  grep -rnw . -e "$1"
}

9. Auto-Completion

Enable auto-completion to speed up command entry:

# Enable programmable completion features
if [ -f /etc/bash_completion ]; then
  . /etc/bash_completion
fi

# Custom completion for aliases
complete -cf sudo
complete -cf man

10. Path Management

Ensure your PATH variable is well-organized:

# Add commonly used directories to PATH
export PATH=$HOME/bin:$HOME/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$PATH

Conclusion

Configuring your .bash_profile is a powerful way to optimize your terminal environment. By implementing these top 10 configurations, you can enhance your productivity, streamline your workflows, and make your command-line experience more enjoyable. Happy coding!

Becoming a Fearless Developer: The Power of Inquiry and Embracing Failure

In the fast-evolving landscape of software development, the journey toward becoming a better developer is as much about honing technical skills as it is about cultivating the right mindset. Central to this growth mindset is the courage to ask questions and the resilience to embrace failure not as a setback, but as a pivotal step toward mastery. This blog post explores the transformative approach of being more brave, inquisitive, and accepting of failure in your development career, underlining how these traits can lead to faster learning and ultimately, better results.

The Courage to Ask Questions

In the realm of development, where new technologies and methodologies emerge at a breakneck pace, the ability to ask questions becomes your greatest asset. Questions are the keys that unlock understanding, clarity, and innovation. Yet, too often, fear of appearing inexperienced or unknowledgeable holds many back from posing questions that could lead to breakthroughs.

Why Asking Questions Matters

  • Accelerates Learning: Every question asked shortens the path to understanding complex concepts or systems.
  • Fosters Collaboration: Questions invite dialogue and can lead to collaborative problem-solving, uniting teams toward common goals.
  • Drives Innovation: Inquiry is at the heart of innovation. Asking “What if?” or “Why not?” can be the genesis of new ideas and improvements.

How to Cultivate the Habit of Asking

  • Embrace Curiosity: Allow your natural curiosity to guide your inquiries. View each coding challenge as an opportunity to learn something new.
  • Normalize Not Knowing: Recognize that not having all the answers is a normal part of the development process. The tech world is too vast for anyone to know everything.
  • Seek Diverse Perspectives: Ask questions to peers, mentors, or the developer community. Different perspectives can offer unique insights and solutions.

Embracing Failure as a Learning Tool

The fear of failure is a formidable barrier to growth. It can lead to playing it safe, avoiding risks, and thus, stagnation. In contrast, adopting a “fail fast, learn fast” mentality encourages taking calculated risks, leading to faster iteration cycles and more innovative solutions.

The Benefits of Failing Fast

  • Quick Feedback Loops: Failing fast means you get immediate feedback on what doesn’t work, allowing you to pivot quickly.
  • Resilience Building: Regularly facing and overcoming failures builds resilience and reduces the fear associated with making mistakes.
  • Enhanced Problem-Solving: Each failure provides unique insights into problems, enhancing your problem-solving skills over time.

Strategies for Failing Forward

  • Set Up Safe-to-Fail Environments: Create or advocate for work environments where experimentation is encouraged, and failures are viewed as learning opportunities.
  • Reflect on Failures: Take time to analyze what went wrong and why. This reflection turns failure into a valuable lesson.
  • Share Learnings: By sharing your failures and what you’ve learned with your team, you contribute to a culture of openness and continuous improvement.

Conclusion: The Fearless Path to Improvement

The journey to becoming a better developer is paved with questions and dotted with failures. It’s a path that requires bravery to admit what you don’t know and resilience to bounce back from setbacks. By fostering a fearless approach to learning—embracing curiosity, the courage to ask questions, and the fortitude to fail forward—you not only accelerate your personal growth but also contribute to a more innovative, collaborative, and resilient tech community. Remember, in the landscape of software development, the most successful are those who are unafraid to question the status quo and learn from every stumble along the way. So, be brave, inquire more, and embrace your failures. Your journey to becoming a better developer depends on it.

Embracing Rubber Duck Debugging in Software Engineering

In the intricate world of software development, where complex problems and pesky bugs are par for the course, developers often find themselves seeking innovative strategies to tackle issues. Enter “Rubber Duck Debugging,” a quirky, yet surprisingly effective technique that has garnered attention and appreciation within the software engineering community. This method, beyond its whimsical name, offers tangible benefits in problem-solving and code comprehension. Let’s dive into the world of rubber duck debugging, explore its benefits, and discuss methods to apply it effectively in your development workflow.

What is Rubber Duck Debugging?

Rubber duck debugging is a method of problem-solving that involves explaining your code, line by line, to an inanimate object – traditionally a rubber duck. The concept is simple: by articulating your thought process and the code’s functionality aloud, you are more likely to uncover mistakes, misunderstandings, or improvements. The technique draws its strength from the act of teaching or explaining, which can clarify your thinking and highlight flaws in logic that were not apparent upon initial inspection.

The Benefits of Rubber Duck Debugging

Enhanced Code Understanding

The act of explaining your code out loud forces you to slow down and consider each part’s purpose and functionality. This deep dive often leads to a better understanding of your own code, potentially revealing better implementation methods or identifying redundant parts.

Improved Problem-Solving Skills

Rubber duck debugging inherently encourages a methodical approach to problem-solving. By verbally navigating through the code and logic, developers practice a structured thought process that can enhance their problem-solving skills over time.

Increased Attention to Detail

Discussing your code line by line can draw attention to the minute details that are easily overlooked. This heightened attention to detail can be crucial in identifying the root causes of bugs or optimizing code performance.

Reduction in Cognitive Load

Externalizing your thought process by talking it out can help reduce the mental burden of holding multiple pieces of information in your head simultaneously. This can free up cognitive resources for more effective problem-solving.

How to Apply Rubber Duck Debugging Effectively

Choose Your Duck

While the traditional choice is a rubber duck, any inanimate object can serve the purpose. The key is to find something you’re comfortable talking to – be it a figurine, a plant, or even a coffee mug.

Isolate the Problem Area

Before you start explaining, narrow down the code segment or functionality that’s causing issues. This focus will make your debugging session more manageable and productive.

Explain Line by Line

Begin explaining your code to your chosen duck from the start of the problem area. Articulate what each line of code is supposed to do, why you chose to implement it in that particular way, and any assumptions you’ve made.

Listen to Yourself

Pay attention to the explanations you’re providing. Often, the mere act of articulating your thoughts can trigger realizations or questions about your approach. Be critical and question each step as if you were an external reviewer.

Take Notes

Keep a notepad or digital document handy to jot down insights, questions, or alternative approaches that come to mind during the process. These notes can be invaluable in revisiting and refining your code.

Iterate

If the first pass doesn’t reveal the issue, go through the process again. Sometimes, repeated explanations can uncover subtleties missed during the initial attempt.

Conclusion

Rubber duck debugging may seem unconventional at first glance, but its effectiveness is rooted in the fundamental principles of learning and communication. By externalizing and articulating your thought process, you engage in a form of active problem-solving that can illuminate issues and enhance your understanding of your code. Whether you’re a seasoned developer or a novice in the field, incorporating rubber duck debugging into your workflow can lead to improved code quality, a deeper understanding of complex problems, and, ultimately, a more efficient and effective development process. So, the next time you’re stuck on a particularly stubborn bug, consider grabbing a rubber duck and start talking.

Maximizing Productivity with the Pomodoro Technique: A Developer’s Guide

In the realm of software development, where focus and productivity are paramount, the Pomodoro Technique has emerged as a popular method for managing time and enhancing concentration. Named after the Italian word for ‘tomato’—inspired by the tomato-shaped kitchen timer used by its inventor, Francesco Cirillo, in the late 1980s—this technique offers a structured approach to work and rest periods. While it’s not a one-size-fits-all solution, its simplicity and effectiveness make it a valuable tool for many developers, especially when tackling intensive coding sessions or battling procrastination. Let’s delve into the Pomodoro Technique, how to implement it, and evaluate its pros and cons.

How to Implement the Pomodoro Technique

The essence of the Pomodoro Technique lies in its cycle of focused work sessions followed by short breaks, promoting sustained concentration and staving off mental fatigue. Here’s a guide to implementing it:

  1. Choose a Task: Start with a clear goal or task you want to accomplish.
  2. Set a Timer for 25 Minutes: Work on your task with undivided attention for 25 minutes. This period is known as one “Pomodoro.”
  3. Take a Short Break (5 Minutes): Once the timer goes off, take a 5-minute break. This is your time to relax—grab a coffee, stretch, or do anything unrelated to work.
  4. Repeat the Cycle: After the break, start another Pomodoro. Continue this cycle four times.
  5. Take a Longer Break (15 Minutes): After completing four Pomodoros, take a longer break of 15 minutes to recharge further before starting the next cycle.

Benefits of the Pomodoro Technique

Improved Focus and Efficiency

By dedicating short, uninterrupted sessions to work, developers can maintain high levels of focus, often leading to more efficient coding and problem-solving.

Enhanced Time Management

The Pomodoro Technique encourages developers to work with the time they have, rather than against it, fostering a sense of urgency that can lead to increased productivity.

Reduced Burnout

Regular breaks help prevent fatigue and burnout, ensuring that developers remain fresh and energized throughout their workday.

Combat Procrastination

The clear structure of work and rest intervals provides a manageable framework that can help even the most notorious procrastinators get started on their tasks.

Potential Drawbacks

Interruption of Flow State

For some tasks, particularly those requiring deep concentration, the interruption every 25 minutes can disrupt the “flow state,” potentially hindering creativity or complex problem-solving.

Not One-Size-Fits-All

Depending on individual work habits or the nature of the task, the standard 25-minute work period may not be optimal for everyone. Some may find shorter or longer intervals more effective.

Rigid Structure

The predefined cycles may not align well with all types of work, especially those requiring flexible time management or collaboration with others who may not be using the technique.

Personalizing the Technique

The beauty of the Pomodoro Technique lies in its flexibility. Feel free to adjust the lengths of both the focus sessions and breaks to better suit your personal rhythm and the nature of your tasks. Some developers may thrive on 50-minute work sessions with 10-minute breaks, while others might prefer shorter intervals.

Conclusion

The Pomodoro Technique is a powerful tool in a developer’s arsenal for enhancing productivity, managing time, and maintaining mental well-being. While it offers numerous benefits, its effectiveness can vary based on individual preferences and the task at hand. Experimenting with and tweaking the technique to find your optimal workflow is key. Whether you’re diving into a complex new project, learning a new programming language, or simply trying to manage your daily tasks more efficiently, the Pomodoro Technique can help you achieve your goals with greater focus and less fatigue.

Harnessing the Power of AI: Unleashing My Full Potential with ChatGPT and GitHub Copilot

Greetings, fellow tech enthusiasts! As a passionate software developer, I’m always on the lookout for tools and techniques to elevate my coding game. Enter ChatGPT and GitHub Copilot—two AI-powered tools that have revolutionised the way I approach programming. In this post, I’ll share my journey of integrating these powerful tools into my daily routine, enhancing my productivity, and learning from them as I go. So, strap in and let’s embark on this exciting voyage together!

The Dynamic Duo ChatGPT and GitHub Copilot

ChatGPT and GitHub Copilot are two groundbreaking AI-driven tools that have taken the developer community by storm. ChatGPT, a state-of-the-art language model, serves as an incredible source of knowledge, while GitHub Copilot, a powerful AI pair programmer, provides real-time code suggestions. By combining these tools, I’ve discovered new ways to tackle development challenges and optimise my learning process. To note, I have both been using these tools since they were first released (ChatGPT this year 2023 and Copilot 2022) and have been closely considering how these tools will shape the future for the “modern developers”.

Boosting Productivity with ChatGPT and GitHub Copilot

  1. Real-time code suggestions: With GitHub Copilot’s context-aware code completions, I can quickly write code, refactor, and debug, saving precious time and effort.
  2. Access to a wealth of knowledge: ChatGPT acts as a reliable companion, helping me find answers to questions, explore new concepts, and dig deeper into complex programming topics.
  3. Continuous learning: As I use these tools, they learn from my coding style, preferences, and patterns, making their suggestions increasingly relevant and personalised.
  4. Enhanced problem-solving: By learning to ask better questions and provide clearer prompts, I can leverage the power of these tools to generate more accurate and efficient solutions to my development challenges.

Learning from AI and Prompting Better Questions

As I continue to use ChatGPT and GitHub Copilot, I’ve realised the importance of asking better questions and providing concise prompts. Here are some tips I’ve picked up along the way:

  1. Be specific: Clearly define the problem or concept you’re trying to understand, making it easier for the AI to provide relevant and helpful responses.
  2. Break down complex problems: Simplify your queries by breaking them into smaller, more manageable chunks, helping the AI understand the context and provide more accurate solutions.
  3. Iterate and refine: If the AI’s initial response isn’t quite what you’re looking for, rephrase your question or provide additional information to guide the AI towards the desired answer.
  4. Learn from examples: Both ChatGPT and GitHub Copilot can generate code examples and explanations. Study these examples to enhance your understanding of concepts and improve your programming skills.

Embracing the Future of Software Development

As I continue to use ChatGPT and GitHub Copilot, I’m not only becoming a more efficient and productive developer but also learning and growing in the process. These AI-driven tools hold immense potential, and with each passing day, I’m excited to unlock new possibilities, refine my skills, and contribute to the ever-evolving world of software development.

  1. Tailored ChatGPT Training: It’s worth considering training different ChatGPT instances for specific purposes. By doing so, you can fine-tune the AI to deliver more focused and relevant responses, catering to your unique development needs.
  2. Quality of Input: While ChatGPT requires a fair amount of input to generate the desired output, this can be significantly improved by asking better questions. It’s essential to understand that the quality of the AI’s response depends on the clarity and specificity of the question asked.
  3. Advantages over Traditional Search Engines: Despite the need for quality input, ChatGPT offers a considerable advantage over traditional search engines. The integration of advanced language models in search engines is paving the way for more accurate and relevant search results, transforming the way developers find solutions and learn new concepts.

Final Note

We’ve explored my journey of using ChatGPT and GitHub Copilot to boost my productivity, enhance my problem-solving skills, and optimise my learning process. By embracing these AI-driven tools, learning to ask better questions, training tailored ChatGPT instances, and recognising their advantages over traditional search engines, I’ve unlocked new levels of efficiency and growth as a developer. As the landscape of software development continues to evolve, other developers and myself will continue to leverage the power of AI to reach new heights and redefine the boundaries of what’s possible.

Why is Lombok a game changing library in Java development?

Java has the reputation of being a verbose language, therefore it is not uncommon to end up with may lines of code, especially for those getter and setters.

Lombok to the rescue?

Lombok is a Java library that directly tackles this issue of code verbosity and repetition, there are many case studies where this significantly reduced the amount of code written, which in turn increase developer productivity!

How do I use Lombok?

Lombok is typically enabled by using either:

  • Gradle
  • Maven
  • Other tools

Maven example:

<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.24</version>
    <scope>provided</scope>
</dependency>

Gradle example:

compileOnly group: 'org.projectlombok', name: 'lombok', version: '1.18.24'

Once this Java library is available, you are then able to utilise Lombok’s helpful annotations, for instance:

Automatic getters, setters, constructors…

@Entity
@Getter @Setter @NoArgsConstructor // <--- Lombok annotations
public class Customer implements Serializable {

    private @Id Long id; // will be set when persisting

    private String firstName;
    private String lastName;
    private int age;

    public Customer(String firstName, String lastName, int age) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }
}

And just like that the @Getter and @Setter annotations generate these for all the fields in the class and for the future fields we want to add on!

Also as you may have spotted, the @NoArgsConstructor will create an empty constructor… even more lines saved!

What if I need to control visibility for some properties!?

Lombok also has you covered, for example lets say if we want to keep our entities id field modifiers package or protected visible, as they maybe expected to be read but not set by the application, there is an annotation configuration that can be used here:

private @Id @Setter(AccessLevel.PROTECTED) Long id;

Lazy Getter

There are also instances where an application requires to expend intensive operations often, which requires the need to save results to get quick access to this data rather than impacting the application throughput.

Let us consider a scenario where we request static data from a database or file. It is generally good practice to:

  • Retrieve this data once
  • Cache the retrieved data, to allow in-memory reads

This is a common pattern and we call this lazy-loading:

  • Retrieve the data, only when it is first needed.
  • In code, this is only get the data when we call the corresponding getter and setter for the first time…

For example, Lombok provides the annotation configuration: @Getter(lazy = true):

public class GetterLazy {

    @Getter(lazy = true)
    private final Map<String, Long> transactions = getTransactions();

    private Map<String, Long> getTransactions() {

        final Map<String, Long> cache = new HashMap<>();
        List<String> txnRows = readTxnListFromFile();

        txnRows.forEach(s -> {
            String[] txnIdValueTuple = s.split(DELIMETER);
            cache.put(txnIdValueTuple[0], Long.parseLong(txnIdValueTuple[1]));
        });

        return cache;
    }
}

As you can see above, this reads some transaction from a file into a Map, as the data is not changing, we will cache it once and enable access via the getter!

Value Classes and Data Transfer Objects (DTOs)

There can be requirements in our applications where we want to create a data types that consist of complex values as (DTOs). It is not uncommon that it is an unchanging immutable data structure.

This could look like a class to represent a successful login operation, which we would like all the fields to be non-null and the object itself to be immutable! This way the fields are thread safe and can be accessed.

public class LoginResult {

    private final Instant loginTs;

    private final String authToken;
    private final Duration tokenValidity;
    
    private final URL tokenRefreshUrl;

    // constructor taking every field and checking nulls

    // read-only accessor, not necessarily as get*() form
}

Similar to the getter and setter annotations outlined earlier, the amount of code that is required would be quite extensive… Lombok could be used here to help this:

@RequiredArgsConstructor
@Accessors(fluent = true) @Getter
public class LoginResult {

    private final @NonNull Instant loginTs;

    private final @NonNull String authToken;
    private final @NonNull Duration tokenValidity;
    
    private final @NonNull URL tokenRefreshUrl;

}

@RequiredArgsConstructor

With the @RequiredArgsConstructor annotation, this creates a constructor with all the final field in the class (just as we declared them)

@NonNull

The @NonNull attributes makes our constructor check for the nullability of the fields being passed, if it is Null is will throw NullPointerException.

@Accessors(fluent=true) 

Since we added @Accessors(fluent=true), this means that we redact the ‘get’ from getAuthToken(), this becomes just authToken() instead.

Java boilerplate methods

Typical boilerplate methods we typically write in our Java classes:

  • toString()
  • equals()
  • hashCode()

These methods are generally created by the help of our IDEs similarly to getters and setter where we auto-generate them from our class attributes. Although again this adds many line of code.

Lombok can automate this by using this annotations within the classes:

  • @ToString
    • Generates a toString() method with all the attributes.
  • @EqualsAndHashCode
    • Generates both equals() and hashCode() methods.

These annotations ship with configurations options, that can provide further control to the developer. For instance, we can just use:

  • (callSuper=true)
    • This parameter will include parent results when generating the method code.

As a demonstration, User JPA entity example includes a reference to events to this associated user:

@OneToMany(mappedBy = "user")
private List<UserEvent> events;

@ToString annotation parameterisation

If this remains in its default configuration the whole list of events will be dumped whenever we call toString() here for our User! (Just because we used the @ToString annotation)

To avoid this, we can parameterise it:

  • @ToString(exclude = {“events”})
    • This will now remove events on the toString() return
    • Also avoid circular references , User reference was within the Event object

@EqualsAndHashCode annotation parameterisation

For the LoginResult example we presented earlier, the equality and hash code calculation may only be need for the token and not for the other final attributes… we can control this with:

  • @EqualsAndHashCode(of =[“authToken”])

All in one short hand annotations!

Now you may get to a point where you are writing multiple annotations to achieve multiple things with your classes, this may become something quite unreadable. But do not worry Lombok has you covered with @Data and @Value.

  • @Data
    • Combination of:
      • @Getter
      • @Setter
      • @RequiredArgsConstructor
      • @ToString
      • @EqualsAndHashCode

Lomboked @Data:

@Data
public class User {
  private Long id;
  private String username;
}

DeLomboked @Data

public class User {
  private Long id;
  private String username;

  public User() {
  }

  public Long getId() {
    return this.id;
  }

  public String getUsername() {
    return this.username;
  }

  public void setId(final Long id) {
    this.id = id;
  }

  public void setUsername(final String username) {
    this.username = username;
  }

  @Override
  public boolean equals(final Object o) {
    if (o == this)
      return true;
    if (!(o instanceof User))
      return false;
    final User other = (User) o;
    if (!other.canEqual((Object) this))
      return false;
    final Object this$id = this.getId();
    final Object other$id = other.getId();
    if (this$id == null ? other$id != null : !this$id.equals(other$id))
      return false;
    final Object this$username = this.getUsername();
    final Object other$username = other.getUsername();
    if (this$username == null ? other$username != null : !this$username.equals(other$username))
      return false;
    return true;
  }

  protected boolean canEqual(final Object other) {
    return other instanceof User;
  }

  @Override
  public int hashCode() {
    final int PRIME = 59;
    int result = 1;
    final Object $id = this.getId();
    result = result * PRIME + ($id == null ? 43 : $id.hashCode());
    final Object $username = this.getUsername();
    result = result * PRIME + ($username == null ? 43 : $username.hashCode());
    return result;
  }

  @Override
  public String toString() {
    return "User(id=" + this.getId() + ", username=" + this.getUsername() + ")";
  }
}
  • @Value
    • Combination of:
      • @Getter
      • @FieldDefaults(makeFinal=true, accessLevel.PRIVATE)
      • @AllArgsConstructor
      • @ToString
      • @EqualsAndHashCode

Lomboked @Value:

@Value
public class User {
  private Long id;
  private String username;
}

DeLomboked @Value

public final class User {
  private final Long id;
  private final String username;

  public User(final Long id, final String username) {
    this.id = id;
    this.username = username;
  }

  public Long getId() {
    return this.id;
  }

  public String getUsername() {
    return this.username;
  }

  @Override
  public boolean equals(final Object o) {
    if (o == this)
      return true;
    if (!(o instanceof User))
      return false;
    final User other = (User) o;
    final Object this$id = this.getId();
    final Object other$id = other.getId();
    if (this$id == null ? other$id != null : !this$id.equals(other$id))
      return false;
    final Object this$username = this.getUsername();
    final Object other$username = other.getUsername();
    if (this$username == null ? other$username != null : !this$username.equals(other$username))
      return false;
    return true;
  }

  @Override
  public int hashCode() {
    final int PRIME = 59;
    int result = 1;
    final Object $id = this.getId();
    result = result * PRIME + ($id == null ? 43 : $id.hashCode());
    final Object $username = this.getUsername();
    result = result * PRIME + ($username == null ? 43 : $username.hashCode());
    return result;
  }

  @Override
  public String toString() {
    return "User(id=" + this.getId() + ", username=" + this.getUsername() + ")";
  }
}

Lombok should not be used everywhere

Notably, from Thorben Janssen blog he outlines issues using @EqualsAndHashCode with JPA entities, the behaviour output of what you are expecting from this can vary and even with a workaround solution he also outlines further issues here.

Furthermore, in the medium article Dont use Lombok by Gonzalo Vallejos indicates there are certain annotations which could be deemed less useful for what they are giving to your application, although to counter this argument, this does not mean you actually have to use everything that Lombok gives you. And to note, it is conclusive in that post that Lombok is still the only solution to reduce boilerplate code, but does have an inherent trade off tight coupling of utilising this annotation heavy library.

Auto Object Composition

Java currently does not have language level constructs to smooth out composition relationships. Lombok’s @Delegate can be useful to achieve this composition relationship.

As of this writing, the @Delegate annotations is still considered to be experimental feature and it has had a negative response from the community as it has not been used much, difficulty supporting edge cases and API is not intuitive at this point according to Project Lombok.

Builder Pattern

Yes, Lombok also allows us to implement the builder pattern very simply with @Builder.

Setting it up:

@Builder
public class ApiClientConfiguration {

    private String host;
    private int port;
    private boolean useHttps;

    private long connectTimeout;
    private long readTimeout;

    private String username;
    private String password;
}

And then this can then be implemented like this:

ApiClientConfiguration config = 
    ApiClientConfiguration.builder()
        .host("api.server.com")
        .port(443)
        .useHttps(true)
        .connectTimeout(15_000L)
        .readTimeout(5_000L)
        .username("myusername")
        .password("secret")
    .build();

Sneaky Exceptions

We can also generate try catch blocks with Lombok using @SneakThrows, more details on this can be found here on the project Lombok site.

Logging

More controversial, as this may not save lines of code in all instances, although it may save word count:

Before:

public class ApiClientConfiguration {
    private static Logger LOG = LoggerFactory.getLogger(ApiClientConfiguration.class);
}

After:

@Slf4j // or: @Log @CommonsLog @Log4j @Log4j2 @XSlf4j
public class ApiClientConfiguration {
}

Thread safe methods

In the past, we used Java synchronized keyword, although this is not completely thread safe implementation. There could be situations, where client code can also synchronize on the same instance, which could lead to unexpected deadlocks.

Lombok has its own annotation for this @Synchronized which improve this situation, we can annotate either static or instance methods and this with auto create private, unexposed fields that our implementation will use for locking!

What happens when we want to Rollback Lombok?

We simply cannot rollback Lombok once it is implemented. In a situation where we may want to rollback we may have to go through multiple classes that have been annotated (Lomboked) and effectively delombok them, which is provided by the same project if needed thankfully, and can be invoked on build of a project.

Conclusion

This blog post does not cover all the features Lombok provides, but does give you an ample taste of what can be utilised here, it undoubtably an incredibly useful library for decreasing lines of code and increasing productivity for Java developers, even with its fallbacks it would be shame to miss out on what it offers here.