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:
build.gradle
.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?
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.'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.
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.
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.
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.
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
.
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.
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.
final
and their fields are also final
. Thus, once a record is created, you cannot modify its state.equals()
, hashCode()
, and toString()
based on the fields of the record.public record Point(int x, int y) { }
This simple declaration creates:
x
and y
.x()
and y()
).equals()
method that checks for equality based on the values of x
and y
.hashCode()
method based on the values of x
and y
.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 } }
While records are meant to be simple data carriers, you can still customize them:
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"); } } }
You can add methods to a record:
public record Point(int x, int y) { public double distanceFromOrigin() { return Math.sqrt(x * x + y * y); } }
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.
No–Args Constructor: JPA typically requires a no–args 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.
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!"); }
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:
isPresent()
The Optional class provides the isPresent() method, which is used to check whether an Optional object contains a non–null 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!"); }