How do I add JUnit to a Maven project?

To add JUnit to a Maven project, you add the JUnit dependency to your project’s pom.xml, create test classes under src/test/java, and run the tests with Maven.

1. Add JUnit to pom.xml

For modern Java projects, use JUnit 5.

<dependencies>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <version>5.13.4</version>
        <scope>test</scope>
    </dependency>
</dependencies>

If your pom.xml already has a <dependencies> section, add only the <dependency> block inside it.

2. Configure Maven Surefire Plugin

JUnit tests are usually run by the Maven Surefire Plugin. Add this inside the <build> section of your pom.xml:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>3.5.3</version>
        </plugin>
    </plugins>
</build>

If your project already has a <build> or <plugins> section, merge the plugin into the existing structure instead of duplicating it.

3. Create a Test Class

Maven expects test classes to be placed under:

src/test/java

Example project structure:

my-project
├── pom.xml
└── src
    ├── main
    │   └── java
    │       └── org
    │           └── kodejava
    │               └── Calculator.java
    └── test
        └── java
            └── org
                └── kodejava
                    └── CalculatorTest.java

Example class to test:

package org.kodejava;

public class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
}

Example JUnit 5 test:

package org.kodejava;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

class CalculatorTest {

    @Test
    void addShouldReturnSum() {
        Calculator calculator = new Calculator();

        int result = calculator.add(2, 3);

        assertEquals(5, result);
    }
}

4. Run the Tests

From the project directory, run:

mvn test

Maven will compile your code, compile your tests, and run any matching test classes.

Common test class naming patterns include:

*Test.java
*Tests.java
*TestCase.java

Complete pom.xml Example

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>org.kodejava</groupId>
    <artifactId>junit-maven-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.release>25</maven.compiler.release>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.13.4</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.5.3</version>
            </plugin>
        </plugins>
    </build>

</project>

That’s it — after adding the dependency and plugin configuration, you can start writing JUnit tests and run them with mvn test.

How do I start unit testing in Java with JUnit?

Unit testing in Java means testing small pieces of code — usually one method or one class — in isolation. The most common testing framework for modern Java projects is JUnit 5, also known as JUnit Jupiter.

This guide shows the basic steps to start writing unit tests with JUnit.


1. Add JUnit to Your Project

If you use Maven, add JUnit 5 to your pom.xml:

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.11.4</version>
    <scope>test</scope>
</dependency>

You should also make sure Maven Surefire can run JUnit 5 tests:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>3.5.2</version>
        </plugin>
    </plugins>
</build>

If you use Gradle, add:

dependencies {
    testImplementation 'org.junit.jupiter:junit-jupiter:5.11.4'
}

test {
    useJUnitPlatform()
}

2. Create a Class to Test

Suppose you have a simple calculator class:

package org.kodejava;

public class Calculator {

    public int add(int a, int b) {
        return a + b;
    }

    public int divide(int a, int b) {
        if (b == 0) {
            throw new IllegalArgumentException("Divider cannot be zero");
        }
        return a / b;
    }
}

This class has two methods:

  • add() returns the sum of two numbers.
  • divide() divides two numbers and rejects division by zero.

3. Create a Test Class

JUnit test classes are usually placed under:

src/test/java

For the Calculator class, create:

src/test/java/org/kodejava/CalculatorTest.java

Example test class:

package org.kodejava;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

class CalculatorTest {

    private final Calculator calculator = new Calculator();

    @Test
    void addReturnsSumOfTwoNumbers() {
        int result = calculator.add(2, 3);

        assertEquals(5, result);
    }

    @Test
    void divideReturnsQuotient() {
        int result = calculator.divide(10, 2);

        assertEquals(5, result);
    }

    @Test
    void divideThrowsExceptionWhenDividerIsZero() {
        IllegalArgumentException exception = assertThrows(
                IllegalArgumentException.class,
                () -> calculator.divide(10, 0)
        );

        assertEquals("Divider cannot be zero", exception.getMessage());
    }
}

4. Understand the Basic JUnit Annotations

The most important annotation is:

@Test

It marks a method as a test method.

Example:

@Test
void addReturnsCorrectResult() {
    assertEquals(4, 2 + 2);
}

Common JUnit 5 annotations include:

Annotation Purpose
@Test Marks a method as a test
@BeforeEach Runs before each test method
@AfterEach Runs after each test method
@BeforeAll Runs once before all tests
@AfterAll Runs once after all tests
@Disabled Temporarily disables a test

Example using @BeforeEach:

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

class CalculatorTest {

    private Calculator calculator;

    @BeforeEach
    void setUp() {
        calculator = new Calculator();
    }

    @Test
    void addReturnsSumOfTwoNumbers() {
        assertEquals(5, calculator.add(2, 3));
    }
}

5. Use Assertions

Assertions check whether the result is what you expect.

Common assertions:

assertEquals(expected, actual);
assertTrue(condition);
assertFalse(condition);
assertNull(value);
assertNotNull(value);
assertThrows(ExceptionType.class, executable);

Example:

package org.kodejava;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

class StringTest {

    @Test
    void stringShouldContainText() {
        String message = "Hello JUnit";

        assertNotNull(message);
        assertTrue(message.contains("JUnit"));
        assertEquals(11, message.length());
    }
}

6. Follow the Arrange, Act, Assert Pattern

A common structure for unit tests is:

  1. Arrange — prepare input data and objects.
  2. Act — call the method being tested.
  3. Assert — verify the result.

Example:

@Test
void addReturnsSumOfTwoNumbers() {
    // Arrange
    Calculator calculator = new Calculator();

    // Act
    int result = calculator.add(2, 3);

    // Assert
    assertEquals(5, result);
}

This makes tests easier to read and maintain.


7. Run the Tests

With Maven:

mvn test

With Gradle:

./gradlew test

Most IDEs also let you right-click the test class or test method and choose Run Test.


8. Naming Test Methods

Use descriptive names, so it is clear what behavior is being tested.

Good examples:

void addReturnsSumOfTwoNumbers()
void divideThrowsExceptionWhenDividerIsZero()
void loginFailsWhenPasswordIsInvalid()

Avoid vague names like:

void test1()
void testAdd()
void shouldWork()

9. What Should You Test?

Good candidates for unit tests include:

  • Business rules
  • Calculations
  • Validation logic
  • Conditional logic
  • Exception handling
  • Data transformation methods

For example, test things like:

discount is applied correctly
invalid email is rejected
zero quantity throws an exception
user cannot withdraw more than their balance

You usually do not need to unit test:

  • Simple getters and setters
  • Framework-generated behavior
  • Code with no meaningful logic
  • External services directly

10. Example: Testing a Realistic Service Class

Class to test:

package org.kodejava.order;

public class DiscountService {

    public double applyDiscount(double price, double discountPercent) {
        if (price < 0) {
            throw new IllegalArgumentException("Price cannot be negative");
        }

        if (discountPercent < 0 || discountPercent > 100) {
            throw new IllegalArgumentException("Discount must be between 0 and 100");
        }

        return price - (price * discountPercent / 100);
    }
}

Test class:

package org.kodejava.order;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

class DiscountServiceTest {

    private final DiscountService discountService = new DiscountService();

    @Test
    void applyDiscountReturnsDiscountedPrice() {
        double result = discountService.applyDiscount(100.0, 10.0);

        assertEquals(90.0, result);
    }

    @Test
    void applyDiscountRejectsNegativePrice() {
        IllegalArgumentException exception = assertThrows(
                IllegalArgumentException.class,
                () -> discountService.applyDiscount(-100.0, 10.0)
        );

        assertEquals("Price cannot be negative", exception.getMessage());
    }

    @Test
    void applyDiscountRejectsInvalidDiscountPercent() {
        assertThrows(
                IllegalArgumentException.class,
                () -> discountService.applyDiscount(100.0, 120.0)
        );
    }
}

For floating-point values, you can also provide a delta:

assertEquals(90.0, result, 0.001);

Summary

To start unit testing in Java with JUnit:

  1. Add JUnit 5 to your project.
  2. Put test classes under src/test/java.
  3. Mark test methods with @Test.
  4. Use assertions such as assertEquals() and assertThrows().
  5. Follow the Arrange, Act, Assert pattern.
  6. Run tests with Maven, Gradle, or your IDE.

A simple JUnit test looks like this:

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

class CalculatorTest {

    @Test
    void addReturnsSum() {
        Calculator calculator = new Calculator();

        assertEquals(5, calculator.add(2, 3));
    }
}

How do I containerize and deploy a Java application with Docker?

Containerizing and Deploying a Java Application with Docker

A typical Java Docker workflow is:

  1. Build the Java application
  2. Package it as a JAR
  3. Create a Docker image
  4. Run the container locally
  5. Push the image to a registry
  6. Deploy it to a server or cloud platform

1. Build Your Java Application

If your project uses Maven, build it with:

mvn clean package

This usually creates a JAR file under:

target/

For example:

target/my-application.jar

If this is a Spring Boot application, the generated JAR is often executable and can be run with:

java -jar target/my-application.jar

2. Create a Dockerfile

Create a file named Dockerfile in the root of your project.

Simple Dockerfile

FROM eclipse-temurin:25-jre

WORKDIR /app

COPY target/*.jar app.jar

EXPOSE 8080

ENTRYPOINT ["java", "-jar", "app.jar"]

What this does

  • FROM eclipse-temurin:25-jre uses a Java 25 runtime image
  • WORKDIR /app sets the working directory inside the container
  • COPY target/*.jar app.jar copies your packaged JAR into the image
  • EXPOSE 8080 documents that the app listens on port 8080
  • ENTRYPOINT starts the Java application

3. Add a .dockerignore File

Create a .dockerignore file to avoid copying unnecessary files into the Docker build context:

.git
.idea
*.iml
target
.DS_Store

If your Dockerfile copies from target/*.jar, you can still ignore most build artifacts carefully, but do not ignore the final JAR unless you use a multi-stage build.

A safer option is:

.git
.idea
*.iml
.DS_Store

4. Build the Docker Image

After running mvn clean package, build the image:

docker build -t my-java-app:1.0 .

You can also tag it as latest:

docker build -t my-java-app:latest .

5. Run the Container Locally

Run the container with:

docker run --name my-java-app -p 8080:8080 my-java-app:1.0

Then open:

http://localhost:8080

If your application uses a different internal port, change the second port value:

docker run -p 8080:9090 my-java-app:1.0

This maps:

host port 8080 -> container port 9090

6. Use Environment Variables

Most real applications need configuration such as database URLs, credentials, profiles, or API keys.

Example:

docker run \
  --name my-java-app \
  -p 8080:8080 \
  -e SPRING_PROFILES_ACTIVE=prod \
  -e DB_URL=jdbc:postgresql://db:5432/appdb \
  my-java-app:1.0

For Spring Boot, common environment variables include:

SPRING_PROFILES_ACTIVE=prod
SERVER_PORT=8080
SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/appdb
SPRING_DATASOURCE_USERNAME=appuser
SPRING_DATASOURCE_PASSWORD=secret

7. Multi-Stage Dockerfile

A better production approach is to build the application inside Docker.

FROM maven:3.9-eclipse-temurin-25 AS build

WORKDIR /app

COPY pom.xml .
COPY src ./src

RUN mvn clean package -DskipTests

FROM eclipse-temurin:25-jre

WORKDIR /app

COPY --from=build /app/target/*.jar app.jar

EXPOSE 8080

ENTRYPOINT ["java", "-jar", "app.jar"]

This gives you:

  • Reproducible builds
  • No need to install Maven locally
  • A smaller final image because Maven is not included in the runtime image

8. Docker Compose Example

If your Java app needs a database, use Docker Compose.

Create docker-compose.yml:

services:
  app:
    build: .
    container_name: my-java-app
    ports:
      - "8080:8080"
    environment:
      SPRING_PROFILES_ACTIVE: docker
      SPRING_DATASOURCE_URL: jdbc:postgresql://db:5432/appdb
      SPRING_DATASOURCE_USERNAME: appuser
      SPRING_DATASOURCE_PASSWORD: secret
    depends_on:
      - db

  db:
    image: postgres:17
    container_name: app-postgres
    environment:
      POSTGRES_DB: appdb
      POSTGRES_USER: appuser
      POSTGRES_PASSWORD: secret
    ports:
      - "5432:5432"
    volumes:
      - postgres-data:/var/lib/postgresql/data

volumes:
  postgres-data:

Run it with:

docker compose up --build

Stop it with:

docker compose down

Remove volumes too:

docker compose down -v

9. Push the Image to a Registry

Tag the image for Docker Hub:

docker tag my-java-app:1.0 your-dockerhub-username/my-java-app:1.0

Log in:

docker login

Push:

docker push your-dockerhub-username/my-java-app:1.0

For GitHub Container Registry:

docker tag my-java-app:1.0 ghcr.io/your-github-username/my-java-app:1.0
docker push ghcr.io/your-github-username/my-java-app:1.0

10. Deploy on a Server

On your server:

docker pull your-dockerhub-username/my-java-app:1.0

Run it:

docker run -d \
  --name my-java-app \
  --restart unless-stopped \
  -p 80:8080 \
  -e SPRING_PROFILES_ACTIVE=prod \
  your-dockerhub-username/my-java-app:1.0

Now your app is available on:

http://your-server-ip

11. Production-Friendly Dockerfile

For a more production-ready Java container, add memory options and a non-root user.

FROM eclipse-temurin:25-jre

WORKDIR /app

RUN addgroup --system appgroup && adduser --system --ingroup appgroup appuser

COPY target/*.jar app.jar

RUN chown appuser:appgroup app.jar

USER appuser

EXPOSE 8080

ENV JAVA_OPTS=""

ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

Run with JVM options:

docker run \
  -p 8080:8080 \
  -e JAVA_OPTS="-Xms256m -Xmx512m" \
  my-java-app:1.0

12. Common Commands

List images

docker images

List running containers

docker ps

List all containers

docker ps -a

View logs

docker logs my-java-app

Follow logs:

docker logs -f my-java-app

Stop container

docker stop my-java-app

Remove container

docker rm my-java-app

Remove image

docker rmi my-java-app:1.0

Open shell in container

docker exec -it my-java-app sh

Recommended Minimal Setup

For most Java web applications, start with these two files.

Dockerfile

FROM eclipse-temurin:25-jre

WORKDIR /app

COPY target/*.jar app.jar

EXPOSE 8080

ENTRYPOINT ["java", "-jar", "app.jar"]

.dockerignore

.git
.idea
*.iml
.DS_Store

Then run:

mvn clean package
docker build -t my-java-app:1.0 .
docker run -p 8080:8080 my-java-app:1.0

That is the basic end-to-end flow for containerizing and deploying a Java application with Docker.

How to Visualize System Components with PlantUML Component Diagrams

To visualize system components using PlantUML Component Diagrams, you’ll need to follow these steps. Component diagrams allow you to model and delineate the architecture of a larger system by showing how components interact with each other.

Steps to Create a Component Diagram with PlantUML

  1. Set Up PlantUML
    To create component diagrams with PlantUML, you need:

    • Java installed
    • PlantUML jar file (or an IDE/plugin with integrated PlantUML support like IntelliJ or VSCode with the PlantUML extension)
    • A rendering tool such as Graphviz (dot).
  2. Start the Diagram
    Specify the start of the diagram using:

    @startuml
    
  3. Define Components
    Each component in the system can be represented with the component keyword. Give each component a meaningful name. Use square brackets or the as keyword to assign aliases/titles to the components:

    component [Component A]
    component "Database" as DB
    
  4. Show Relationships Between Components
    Use arrows (--> or --) to represent interfaces, dependencies, or flows between components:

    [Frontend] --> [Backend]
    [Backend] --> DB
    
  5. Group Components (Optional)
    Use package to group logically related components:

    package "User Interface" {
       [Frontend]
       component "Authentication Module" as AuthModule
    }
    
  6. Icons for Common Elements (Optional)
    You can use PlantUML’s built-in stereotypes to enhance clarity by showing commonly used icons:

    component [Cloud Service] <<cloud>>
    component [Database] <<database>>
    
  7. End the Diagram
    Close the diagram with:

    @enduml
    

Example: Simple Component Diagram

Here’s a complete example that shows an e-commerce system with a frontend, backend, and database:

@startuml
title E-Commerce System Architecture

package "User Interface" {
    [Frontend]
}

package "Business Logic" {
    component "Authentication Service" as AuthService
    component "Product Service" as ProductService
}

package "Data Layer" {
    [Database] <<database>>
}

[Frontend] --> AuthService : authenticate()
[Frontend] --> ProductService : fetch products
AuthService --> [Database] : verify credentials
ProductService --> [Database] : query data

@enduml

Render the Diagram

  • Run the PlantUML jar file or use an IDE plugin to generate the component diagram as an image (PNG, SVG, etc.).
  • Use online tools such as PlantUML Server or integrated plugins in IDEs.

Output

The diagram will illustrate:

  • Frontend interacting with services in the backend.
  • Backend services communicating with the database.
  • Logical groupings (packages) of components.

By following these steps, you can easily model and abstract complex systems to identify dependencies, cohesion, and interactions clearly.

How to Design Package Diagrams Using PlantUML for Modular Architecture

In a modular architecture, package diagrams are a powerful way to represent the dependencies and relationships between different modules or packages within a system. With PlantUML, you can easily create package diagrams to visually describe your architecture and ensure modularity principles like separation of concerns, low coupling, and high cohesion are maintained.
Here’s how you can design package diagrams using PlantUML for modular architecture:

1. Understanding the Components of Package Diagrams

Before creating the diagram, it’s important to understand the following key elements:

  • Packages: Represent logical groupings of classes, modules, or functionalities.
  • Dependencies: Links between packages show directional relationships (e.g., which package depends on another).
  • Hierarchies: You can nest packages inside others to show submodules or grouped components.

2. Basic PlantUML Syntax for Package Diagrams

PlantUML provides simple syntax for creating package diagrams using keywords like package, namespace, and component.

Example Syntax:

@startuml
package "Module 1" {
  [Class1]
  [Class2]
}

package "Module 2" {
  [Class3]
}

[Class1] --> [Class3] : Uses
@enduml

3. Steps for Designing Modular Architecture Package Diagram

Follow these steps to design a package diagram for modular architecture:

Step 1: Identify Modules or Layers

List all high-level modules or layers of your system (e.g., UI Layer, Business Logic Layer, Data Access Layer, etc.).

Step 2: Define Submodules

Break each module into its submodules or components (e.g., User Management Module inside Business Logic Layer).

Step 3: Show Dependencies

Draw directional relationships between modules. Ensure dependencies only flow in one direction to avoid circular links.

Step 4: Apply Abstractions

Use abstractions like interfaces and package hierarchy to reduce direct dependencies between modules.

4. PlantUML Example: Modular Architecture

Here’s an example of a modular architecture package diagram using PlantUML:

@startuml
title Modular Architecture Package Diagram

package "UI Layer" {
  [LoginScreen]
  [Dashboard]
}

package "Business Logic Layer" {
  [UserService]
  [OrderService]
}

package "Data Access Layer" {
  [UserRepository]
  [OrderRepository]
}

[LoginScreen] --> [UserService] : Uses
[Dashboard] --> [OrderService] : Displays Data
[UserService] --> [UserRepository] : Accesses Data
[OrderService] --> [OrderRepository] : Accesses Data

@enduml

This example demonstrates:

  1. Abstract layers to separate responsibilities (UI, Business Logic, Data Access).
  2. Directional dependencies to reduce coupling.
  3. Components grouped logically by their roles.

5. Advanced Features

PlantUML allows you to incorporate advanced features in package diagrams:

  • Nested Packages: Nest submodules within a parent module to show hierarchical relationships.
  @startuml
  package "Business Logic Layer" {
    package "User Management" {
      [UserService]
      [UserValidator]
    }

    package "Order Management" {
      [OrderService]
      [OrderValidator]
    }
  }
  @enduml
  • Styling Packages: You can customize the styles for better visuals.
  @startuml
  package "Module A" #LightBlue {
    [Component1]
    [Component2]
  }

  package "Module B" #LightGreen {
    [Component3]
  }

  [Component1] --> [Component3]
  [Component2] --> [Component3]
  @enduml
  • Interfaces in Packages: Use interface to show exposed functionality.
  @startuml
  package "Business Logic Layer" {
    interface IOrderService
    [OrderService]
    IOrderService <|.. [OrderService]
  }

  [UI] --> IOrderService
  @enduml

6. Best Practices for Modular Architecture

  • Minimize Coupling: Ensure packages communicate only via interfaces or well-defined dependencies.
  • High Cohesion: Group related functionalities together in the same package.
  • Avoid Circular Dependencies: Acyclic dependencies promote better maintainability.
  • Group by Layers: Prefer logical layers (e.g., presentation, domain, infrastructure).
  • Add Descriptions: Use notes for additional descriptions.

7. Tools for Generating Package Diagrams

You can generate diagrams directly from PlantUML-text files or integrate with tools like:

  • IntelliJ IDEA (with PlantUML plugin)
  • Visual Studio Code (with PlantUML extension)
  • Online tools like PlantUML Editor

By following these practices and using the examples, you can effectively design modular architecture package diagrams using PlantUML.