Subscribe To Our Newsletter
You will receive our latest post and tutorial.
Thank you for subscribing!

required
required


Spring Boot with Gradle

Gradle is a powerful and flexible build automation system that uses a Groovy-based DSL (Domain Specific Language) or Kotlin-based DSL for defining build tasks. It was designed to overcome the shortcomings of Apache Ant and Apache Maven, and it is widely adopted in the Java community, as well as in Android app development.

Here’s a breakdown of Gradle’s key features and how it works:

  1. Build Scripts: Gradle uses scripts to define and configure builds. These scripts are typically written in Groovy or Kotlin and are interpreted by Gradle. A common name for a build script file is build.gradle.
  2. Project and Task Structure: A Gradle build is made up of one or more projects. Each project can have one or more tasks. For example, a task might compile source files, create a JAR file, or generate Javadoc documentation.
  3. Plugins: Gradle is designed to be extensible, and plugins provide a way to add new functionality. Many plugins are available for Java, Android, and other platforms or tools.
  4. Dependencies: Like Maven, Gradle has robust dependency management capabilities. You can declare dependencies in your build script, and Gradle will download and cache those dependencies for you.
  5. Build Lifecycles: Gradle has a lifecycle for its builds which includes phases like initialization, configuration, and execution. During these phases, different actions are performed, like determining which projects are part of the build, determining dependencies, and executing tasks.
  6. Incremental Builds: Gradle avoids redundant work by only executing tasks that are out-of-date. For instance, if you re-run a build without changing any source files, Gradle recognizes that there’s no need to recompile the source and skips that task.
  7. Customizability: One of Gradle’s strengths is its flexibility. You can create custom tasks, add custom logic, and modify the build process as needed.
  8. Wrapper: Gradle provides a wrapper (typically called gradlew on UNIX-like systems or gradlew.bat on Windows). This is a small script that can download and install the correct version of Gradle for a project, ensuring consistent builds across different environments.

How does it work?

  1. When you run a Gradle command, it starts by evaluating the build script.
  2. It initializes the build, determining which projects will be built.
  3. During the configuration phase, all tasks are configured. At this stage, no task is executed; Gradle just determines the dependencies and order in which tasks will run.
  4. During the execution phase, Gradle runs the necessary tasks in the order determined by their dependencies.
  5. As tasks run, Gradle checks its cache to avoid redoing work (like recompiling unchanged source files).
  6. Once all tasks are executed, the build finishes.

Gradle is a versatile tool that can be used not only for Java projects but also for C++, Python, and more. It has integrations with many IDEs (like IntelliJ IDEA and Android Studio) and CI/CD tools, making it a popular choice for modern development environments.

How to run tests using gradle?

in maven -> mvn test

gradle test

or 

./gradlew test

How to compile java project using gradle?

in maven -> mvn compile

gradle compileJava

or 

./gradlew compileJava

How to package a springboot project using gradle?

in maven -> mvn package

gradle jar

or

./gradlew jar

How to skip tests using gradle?
in maven -> mvn install -DskipTests

gradle assemble -x test

or 

./gradlew assemble -x test

How to list dependencies using gradle?
in maven -> mvn dependency:list

gradle dependencies

or 

./gradlew dependencies

Including a dependency in a Gradle project with Spring Boot typically involves adding the dependency to the build.gradle file. Here’s a step-by-step process:

Ensure You Have the Spring Boot Gradle Plugin:

First, make sure your build.gradle file applies the Spring Boot Gradle plugin and has the Spring Boot dependencies’ BOM (Bill Of Materials):

plugins {
    id 'org.springframework.boot' version '2.5.4' // Use the appropriate version
    id 'io.spring.dependency-management' version '1.0.11.RELEASE' // Again, ensure the version matches your needs
    id 'java'
}

Include the Spring Boot Repositories (if not already added):

Spring Boot dependencies are available in the Maven Central repository, but if you’re using Spring Milestone or Snapshot releases, you might also need the Spring repositories:

repositories {
    mavenCentral()
    // For milestone releases
    maven { url 'https://repo.spring.io/milestone' }
    // For snapshots
    maven { url 'https://repo.spring.io/snapshot' }
}

Add Your Dependency:

Let’s say you want to include Spring Web to create a web application. You would add:

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    // ... your other dependencies
}

Note:

  • implementation is a configuration indicating that the dependency is required for compiling the source code of the module and is also required at runtime.
  • The format for the dependency is 'group:artifact:version', but when using the Spring Boot BOM, you often don’t need to specify the version since it’s managed by Spring Boot’s dependency management.

 

Refresh Your Dependencies:

If you’re using an IDE like IntelliJ IDEA or Eclipse, you might need to refresh your project to download the new dependencies. For command-line builds, the next time you run a Gradle task like ./gradlew bootRun, the dependencies will be fetched automatically.

Additional Tip:

To view all the managed versions and dependencies provided by Spring Boot, you can check the spring-boot-dependencies BOM in the Maven Central repository. It’s a useful reference when you want to know the default versions for various libraries that Spring Boot manages.

Remember, the versions and dependencies might vary based on the specific version of Spring Boot you’re using, so always refer to the official Spring Boot documentation or the Spring Initializr website for accurate information.

 

Github Repo Reference

August 10, 2023

Java Sealed Class

Sealed classes, introduced in Java 15 as a preview feature and later refined in subsequent releases, represent a middle ground between the two extremes of the class world: the completely open public class, and the completely closed final class.

What is a Sealed Class?

A sealed class restricts which other classes or interfaces can extend or implement it. It’s defined using the sealed, non-sealed, or permits modifier.

Basic Syntax:

public sealed class Shape 
    permits Circle, Rectangle, Triangle { }

In this example, Shape is a sealed class, and only Circle, Rectangle, and Triangle can extend it.

Using Sealed Classes:

Defining:

public sealed class Animal 
    permits Dog, Cat { }

public final class Dog extends Animal { }

public final class Cat extends Animal { }

Note: Classes extending a sealed class can be final, sealed, or non-sealed.

Benefits (Advantages):

  1. Controlled Hierarchies: You have greater control over how your class is used and extended. It’s particularly useful when you want to restrict subclasses for a specific domain problem.
  2. Pattern Matching: In conjunction with Java’s pattern matching features, sealed classes offer more robust compile-time checks. The compiler can verify if all permitted subclasses are covered in a pattern-matching block, allowing for exhaustive pattern matching.
  3. Maintainability: By restricting which classes can extend your class, you can avoid potential future complications when your codebase evolves or is refactored.

Drawbacks (Disadvantages):

  1. Flexibility: Sealed classes can reduce the flexibility for developers, especially if they want to extend a class in a way that wasn’t anticipated by the original developer.
  2. Complexity: For smaller projects or cases where exhaustive control isn’t needed, using sealed classes might introduce unnecessary complexity.

When to Use:

  1. Domain Modeling: When modeling a specific domain where you know all the possible subclasses, sealed classes can be beneficial. For example, modeling geometric shapes, AST nodes in compilers, or specific kinds of events in an event system.
  2. API Design: If you’re designing an API and want to ensure consumers only extend specific parts of your hierarchy, sealed classes can offer that control.
  3. Exhaustive Pattern Matching: When using pattern matching and you want to ensure all possible cases are handled.

When Not to Use:

  1. Open-ended Scenarios: If you believe there’s a chance that other developers might need to extend your class in ways you haven’t thought of, it’s best not to seal it.
  2. For Simplicity: For simple projects or basic class hierarchies where exhaustive control isn’t beneficial, adding sealed class restrictions might be overkill.

Conclusion:

Sealed classes are a powerful addition to the Java language, offering a level of control over inheritance that wasn’t previously available. Like all features, they have their pros and cons, and the decision to use them should be based on the specific needs of the project and the problems you’re trying to solve.

July 27, 2023

Java Record

What is a Record?

In Java, it’s common to have classes that exist solely to carry data, i.e., classes with fields, simple methods like getters, equals(), hashCode(), and toString(). Before records, defining such classes could be quite verbose.

A record is a restricted form of class that provides a concise way to define such classes. When you define a record, the Java compiler automatically implements several standard methods for you.

Characteristics of Records:

  1. Immutable: Records are implicitly final and their fields are also final. Thus, once a record is created, you cannot modify its state.
  2. No Subclassing: You cannot explicitly extend a record, and records cannot extend other classes.
  3. Auto-generated Standard Methods: The compiler automatically generates methods like equals(), hashCode(), and toString() based on the fields of the record.
  4. Explicit fields: The record’s header declares its fields, and these are the only fields allowed.
public record Point(int x, int y) { }

This simple declaration creates:

  • A record with two fields, x and y.
  • A constructor taking two parameters to initialize these fields.
  • Getters for these fields (x() and y()).
  • An equals() method that checks for equality based on the values of x and y.
  • A hashCode() method based on the values of x and y.
  • A toString() method that returns a string in the format Point[x=<value>, y=<value>].
public class TestRecords {
    public static void main(String[] args) {
        Point p1 = new Point(5, 10);
        Point p2 = new Point(5, 10);
        
        System.out.println(p1); // Prints: Point[x=5, y=10]
        System.out.println(p1.equals(p2)); // Prints: true
    }
}

Customizing Records:

While records are meant to be simple data carriers, you can still customize them:

Custom Constructors:

You can define custom constructors, but they should call the canonical constructor:

public record Rectangle(int width, int height) {
    public Rectangle {
        if (width < 0 || height < 0) {
            throw new IllegalArgumentException("Dimensions should be positive");
        }
    }
}

Adding Methods:

You can add methods to a record:

public record Point(int x, int y) {
    public double distanceFromOrigin() {
        return Math.sqrt(x * x + y * y);
    }
}

Implementing Interfaces:

Records can implement interfaces:

public record NamedPoint(int x, int y, String name) implements Comparable<NamedPoint> {
    @Override
    public int compareTo(NamedPoint other) {
        return name.compareTo(other.name);
    }
}

Things to Remember:

Record components are final by default.

Records can’t be abstract.

Records can’t extend other classes.

You can’t declare instance fields in a record that aren’t record components.

Records are a significant step toward making Java more expressive and less verbose, especially for common coding patterns.

Caveats and Considerations for using Record with JPA:

Annotations: With records, you can’t annotate fields directly since they’re declared in the record header. If you need to add JPA annotations (like @Id, @GeneratedValue, etc.), they should be added to the accessor methods (getters) of the record.

Immutability: Records are inherently immutable, which means all their fields are final. This behavior aligns with the principle of immutability in entity design but can complicate scenarios where you want to change an entity’s state after it has been constructed.

NoArgs Constructor: JPA typically requires a noargs constructor, which records don’t provide out of the box. Hibernate’s (a popular JPA implementation) team has been working to support records, so expect improvements in this area. If your JPA provider doesn’t support records yet, consider using DTOs (Data Transfer Objects) alongside your entities.

Experimental Support: As mentioned earlier, while Spring Data started introducing experimental support for records, this might not be fully mature or cover all scenarios. Always refer to the latest Spring Data documentation and check for compatibility and best practices.

July 27, 2023

Java – Method Reference

Java method references are a shorthand notation of a lambda expression to call a method. They became available in Java 8, just like lambdas and streams. Method references allow for a cleaner and more readable way to refer to methods without executing them.

Static Method Reference

a reference to a static method is a type of method reference that allows you to reference and use a static method of a class as a functional interface. It is denoted by ClassName::staticMethodName. This type of method reference is useful when you want to pass a static method as an argument to a functional interface or use it in lambda expressions.

ClassName::staticMethodName

static void staticMethodReference() {
    System.out.println("\nstaticMethodReference...");

    // Using lambda expression
    Arrays.asList("apple", "banana", "orange").forEach(str -> System.out.println(str.toUpperCase()));

    // Using method reference
    Arrays.asList("apple", "banana", "orange").forEach(Java8MethodReference::convertToUpperCase);

    // using static method to print out
    users.forEach(System.out::println);

    System.out.println("staticMethodReference done!");
}

public static void convertToUpperCase(String str) {
    System.out.println(str.toUpperCase());
}

Instance Method Reference

a reference to an instance method is a type of method reference that allows you to reference and use an instance method of a particular object as a functional interface. It is denoted by instance::instanceMethodName. This type of method reference is useful when you want to pass an instance method as an argument to a functional interface or use it in lambda expressions.

instance::instanceMethodName

static void instanceMethodReference() {
    System.out.println("\ninstanceMethodReference...");

    List<String> fruits = Arrays.asList("apple", "banana", "orange");

    // Using lambda expression
    fruits.forEach(str -> {
        System.out.println("str: " + str + ",length: " + str.length());
    });

    Java8MethodReference java8MethodReference = new Java8MethodReference();

    // Using method reference
    fruits.forEach(java8MethodReference::printLength);

    System.out.println("instanceMethodReference done!");
}

public void printLength(String str) {
    System.out.println("str: " + str + ",length: " + str.length());
}

Constructor Method Reference

a reference to a constructor is a type of method reference that allows you to reference and use a constructor of a class as a functional interface. It is denoted by ClassName::new. This type of method reference is useful when you want to pass a constructor as an argument to a functional interface or use it in lambda expressions.

ClassName::new

static void constructorMethodReference() {
    System.out.println("\nconstructorMethodReference...");

    // Using lambda expression
    Supplier<User> personLambda = () -> User.builder().firstName("John").lastName("Doe").build();
    User johnFromLambda = personLambda.get();
    System.out.println("User from lambda: " + johnFromLambda);

    // Using method reference
    Supplier<User> personMethodRef = User::new;
    User johnFromMethodRef = personMethodRef.get();
    System.out.println("User from method reference: " + johnFromMethodRef);

    System.out.println("constructorMethodReference done!");
}

 

July 27, 2023

Java – Optional

Java Optional is a class that represents a value that may or may not be present. It is used to avoid NullPointerExceptions when dealing with nullable values.

Optional is a class introduced to handle the absence of a value in a more elegant and safe way. It is designed to avoid null pointer exceptions and make it clear when a value may be absent. An Optional object can either contain a non-null value or represent that no value is present.

Before Java 8, when a method could return a null value, developers had to explicitly check for null before performing any operations on the returned value. This often led to cluttered and error-prone code. With Optional, you can avoid explicit null checks and handle the absence of values in a more functional and streamlined manner.

The Optional class provides several methods to interact with and manipulate the contained value, such as get(), isPresent(), orElse(), orElseGet(), orElseThrow(), and more.

Here’s a simple example to demonstrate the use of Optional:

import java.util.Optional;

public class OptionalExample {
    public static void main(String[] args) {
        String name = "John"; // Change this to null to see the difference

        // Creating an Optional object from the value
        Optional<String> optionalName = Optional.ofNullable(name);

        // Checking if a value is present
        if (optionalName.isPresent()) {
            System.out.println("Name: " + optionalName.get());
        } else {
            System.out.println("Name is not present.");
        }

        // Using orElse to provide a default value if the value is absent
        String defaultName = optionalName.orElse("Unknown");
        System.out.println("Default Name: " + defaultName);

        // Using orElseGet to provide a default value using a supplier if the value is absent
        String otherDefaultName = optionalName.orElseGet(() -> "Anonymous");
        System.out.println("Other Default Name: " + otherDefaultName);
    }
}

Output when name = "John":

Name: John
Default Name: John
Other Default Name: John

Output when name = null:

Name is not present.
Default Name: Unknown
Other Default Name: Anonymous

In this example, we create an Optional object using the static ofNullable() method. If the value is present, we can retrieve it using the get() method. However, it is advisable to use the isPresent() method before calling get() to avoid a NoSuchElementException. Alternatively, you can use the orElse() method to provide a default value if the Optional object is empty.

The Optional class encourages better code design by making it explicit when a value may be absent and providing clear methods for handling both present and absent cases.

It’s essential to use Optional wisely and not overuse it. It is best suited for method return types and fields that might be absent. Avoid using Optional as a replacement for traditional null checks within method bodies.

Here are some of the benefits of using Java Optional:

  • It helps to avoid NullPointerExceptions.
  • It makes code more readable and maintainable.
  • It can be used to represent a variety of different scenarios, such as when a value is not yet known, when a value may be null, or when a value is optional.

isPresent()

The Optional class provides the isPresent() method, which is used to check whether an Optional object contains a nonnull value or is empty (contains a null value). The isPresent() method returns a boolean value indicating whether the Optional object holds a value.

static void isPresent() {
    System.out.println("isPresent...");

    String firstName = faker.name().firstName();
    String lastName = faker.name().lastName();
    String email = (firstName + lastName).toLowerCase() + "@gmail.com";

    User user = User.builder().firstName(firstName).lastName(lastName).email(email).phoneNumber(faker.phoneNumber().cellPhone()).build();

    System.out.println("User: " + user.toString());

    Optional<User> optUser = Optional.ofNullable(user);

    // Checking if the Optional object contains a value
    if (optUser.isPresent()) {
        System.out.println("User is present: " + optUser.get());
    } else {
        System.out.println("User is not present.");
    }

    System.out.println("isPresent done!");
}

The output

isPresent...
User: User(id=0, firstName=Natalie, lastName=Braun, email=nataliebraun@gmail.com, phoneNumber=975.490.2241)
User is present: User(id=0, firstName=Natalie, lastName=Braun, email=nataliebraun@gmail.com, phoneNumber=975.490.2241)
isPresent done!

ifPresentOrElse()
The Optional class introduced the ifPresentOrElse() method. This method provides a concise way to perform an action if the Optional object contains a value, and an alternative action if the Optional object is empty.

static void ifPresentOrElse() {
    System.out.println("ifPresentOrElse...");

    String firstName = faker.name().firstName();
    String lastName = faker.name().lastName();
    String email = (firstName + lastName).toLowerCase() + "@gmail.com";

    User user = User.builder().firstName(firstName).lastName(lastName).email(email).phoneNumber(faker.phoneNumber().cellPhone()).build();

    System.out.println("User: " + user.toString());

    Optional<User> optUser = Optional.ofNullable(user);

    optUser.ifPresentOrElse(u -> {
        System.out.println("user is present");
        System.out.println("User: " + u.toString());
    }, () -> {
        System.out.println("else");
    });

    user = null;
    optUser = Optional.ofNullable(user);

    optUser.ifPresentOrElse(u -> {
        System.out.println("user is present");
    }, () -> {
        System.out.println("user is null");
    });

    System.out.println("ifPresentOrElse done!");
}

ofElse()

static void orElse() {
    System.out.println("orElse...");

    String firstName = faker.name().firstName();
    String lastName = faker.name().lastName();
    String email = (firstName + lastName).toLowerCase() + "@gmail.com";

    User user = User.builder().firstName(firstName).lastName(lastName).email(email).phoneNumber(faker.phoneNumber().cellPhone()).build();

    System.out.println("User: " + user.toString());

    Optional<User> optUser = Optional.ofNullable(user);

    User u = optUser.orElse(User.builder().firstName("Test").build());

    System.out.println("User firstName: " + u.getFirstName());

    user = null;
    optUser = Optional.ofNullable(user);

    u = optUser.orElse(User.builder().firstName("Test").build());

    System.out.println("User firstName: " + u.getFirstName());

    System.out.println("orElse done!");
}

filter()

The Optional class provides the filter() method, which is used to conditionally process the value contained within the Optional. The filter() method checks if the Optional contains a value that matches a given predicate (a condition), and if so, it returns the same Optional object. If the Optional is empty (contains a null value) or the predicate evaluates to false for the value, the filter() method returns an empty Optional.

static void filter() {
    System.out.println("filter...");

    String firstName = faker.name().firstName();
    String lastName = faker.name().lastName();
    String email = (firstName + lastName).toLowerCase() + "@gmail.com";

    User user = User.builder().firstName(firstName).lastName(lastName).email(email).phoneNumber(faker.phoneNumber().cellPhone()).build();

    System.out.println("User: " + user.toString());

    user = null;

    Optional<User> optUser = Optional.ofNullable(user);

    optUser = optUser.filter(u -> {
        System.out.println("running filter if value is not null");
        return u.getFirstName().length() > 0;
    });

    optUser.ifPresent(u -> {
        System.out.println("User firstName: " + u.getFirstName());
    });

    System.out.println("filter done!");
}

map()

static void map() {
    System.out.println("map...");

    String firstName = faker.name().firstName();
    String lastName = faker.name().lastName();
    String email = (firstName + lastName).toLowerCase() + "@gmail.com";

    User user = User.builder().firstName(firstName).lastName(lastName).email(email).phoneNumber(faker.phoneNumber().cellPhone()).build();

    System.out.println("User: " + user.toString());

    user = null;

    Optional<User> optUser = Optional.ofNullable(user);

    Optional<String> optEmail = optUser.map(u -> {
        System.out.println("running map if value is not null");
        return u.getEmail();
    });

    optEmail.ifPresent(em -> {
        System.out.println("User email: " + em);
    });

    System.out.println("map done!");
}

 

Github Code Reference

July 24, 2023