Build a Resource Readiness Rules Engine

Build a Java rules engine that explains resource readiness with tests and CI.

Introduction

30 Second Summary

Important decisions often collapse into a single yes or no. When that answer has no explanation, nobody knows which requirement failed or what to fix.

In this project, you will build a Java rules engine that explains whether a fictional resource is ready. Each requirement becomes an independent rule with a visible result.

What You'll Build

Running the finished app produces a line-by-line report showing Capacity: PASS, Inspection: FAIL, and Overall readiness: NOT READY.

By the end of this project, you'll have:

  • A readiness report that names every passing or failing rule before showing the overall result.
  • An extensible rule design where a Java interface lets you add requirements without rewriting the evaluator.
  • A repeatable safety net of two JUnit tests that prove both ready and not-ready behavior.
  • Secret Mission: Publish the project to a public GitHub repository. GitHub Actions will rerun the Maven tests on every push or pull request to main.

Are there any prerequisites?

You should be comfortable with basic Java syntax, classes, methods, conditionals, and terminal navigation. The first step verifies your JDK and Maven installation before you create any project files.

Before We Start

Before the hands-on work begins, this is your moment to lock in what you are building. A clear purpose will guide every design decision that follows.

Verify the Toolchain and Create the Maven Project

Your rules engine needs a known Java baseline before every build. Maven provides a repeatable path from source files to compiled output.

Git completes the local toolchain. IntelliJ IDEA gives the project a clear place for its configuration plus source folders.

In this step, get ready to:
  • Verify Java 17 or newer in macOS Terminal.
  • Verify Maven 3.9 plus Git.
  • Create the Maven project in IntelliJ IDEA.
Verify Java, Maven, and Git

A JDK supplies the compiler that turns Java source files into runnable classes. This project requires Java 17 or newer.

  • Press Cmd+Space to open macOS search.
  • Type Terminal into the search bar.
  • Press Enter to open macOS Terminal.
  • Check the installed JDK by running this command:
java -version

What Does This Command Check?

The command asks the active Java runtime to report its version. The result confirms whether your terminal can use the required JDK.

✔️ I See Java 17 or Newer

Your Java baseline is ready. The compiler can support this project's release target.

ⓧ I See a Version Older Than 17

The installed JDK is below the project baseline. Homebrew provides the Eclipse Temurin 17 package used in this project.

  • Install Homebrew if it is missing by running its official installer:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

What Does This Installer Do?

The command downloads the official Homebrew installer. The installer explains each change before applying it to your Mac.

  • Install Eclipse Temurin 17 by running this command:
brew install --cask temurin@17

What Does This Command Install?

Homebrew installs the Eclipse Temurin 17 JDK. This gives the project a compatible Java compiler plus runtime.

  • Select Java 17 for the current Terminal session by running this command:
export JAVA_HOME=$(/usr/libexec/java_home -v 17)

What Does This Command Change?

The command points JAVA_HOME at the installed Java 17 JDK. Maven can now use the same JDK during this Terminal session.

  • Verify the active Java version by running the check again:
java -version

What Should You See?

The version output now reports Java 17 or newer. This confirms that the current Terminal session uses a compatible JDK.

Still Seeing the Older JDK?

Confirm that the Temurin installation completed before setting JAVA_HOME. Rerun the export command in the same Terminal window that you use for Maven.

If Java still reports the older version, help me select Eclipse Temurin 17 in macOS Terminal.

ⓧ Java Is Not Available

Your Terminal cannot currently reach a JDK. Installing Eclipse Temurin 17 supplies the required compiler plus runtime.

  • Install Homebrew if it is missing by running its official installer:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

What Does This Installer Do?

The command downloads the official Homebrew installer. The installer guides you through the macOS package manager setup.

  • Install Eclipse Temurin 17 by running this command:
brew install --cask temurin@17

What Does This Command Install?

Homebrew installs the Eclipse Temurin 17 JDK. The JDK provides the tools needed to compile this project.

  • Select Java 17 for the current Terminal session by running this command:
export JAVA_HOME=$(/usr/libexec/java_home -v 17)

What Does This Command Change?

The command sets JAVA_HOME to the installed Java 17 JDK. Commands launched from this Terminal session can now find it.

  • Confirm that Java is available by running this command:
java -version

What Should You See?

The command now reports Java 17 or newer. Your Terminal can use the project JDK.

Java Still Unavailable?

Confirm that the Temurin installation finished successfully. Run the JAVA_HOME command again in the same Terminal window.

If Java remains unavailable, help me diagnose my Temurin installation.

Maven reads the project configuration before compiling any source files. Its version output also identifies the Java runtime used for the build.

  • Check the installed Maven version by running this command:
mvn -v

What Does This Command Check?

  • The Maven line identifies the installed Maven release.
  • The Java line identifies the JDK Maven uses for builds.

✔️ I See Maven 3.9.x

Maven is ready. Confirm that its Java line also reports version 17 or newer.

ⓧ I See Another Maven Version

This project uses the stable Maven 3.9 release line. Homebrew can update its installed Maven formula.

  • Upgrade the Homebrew-managed Maven installation by running this command:
brew upgrade maven

What Does This Command Change?

Homebrew upgrades the installed Maven formula to its current stable release. The project can then use the expected Maven 3.9 behavior.

  • Confirm the upgraded Maven version by running this command:
mvn -v

What Should You See?

Maven reports version 3.9.16 or another compatible 3.9 release. Its Java line reports version 17 or newer.

Maven Did Not Upgrade?

The upgrade command expects Maven to be managed by Homebrew. A separately installed Maven distribution may need to be removed from your shell path first.

If the old release remains active, help me identify which Maven installation my Mac is using.

ⓧ Maven Is Not Available

Maven still needs to be installed before the project can compile consistently. Homebrew provides the current stable Maven release.

  • Install Homebrew if it is missing by running its official installer:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

What Does This Installer Do?

The command downloads the official Homebrew installer. Homebrew then manages the Maven installation used in this project.

  • Install Maven by running this command:
brew install maven

What Does This Command Install?

Homebrew installs the current stable Maven release. Maven uses the JDK already selected in your Terminal session.

  • Verify Maven plus its active Java runtime by running this command:
mvn -v

What Should You See?

Maven reports version 3.9.16 or another compatible 3.9 release. Its Java line reports version 17 or newer.

Maven Still Unavailable?

Confirm that the Homebrew installation completed. Reopen Terminal if the installer asked you to update your shell environment.

If Maven remains unavailable, help me diagnose my Maven installation.

The final toolchain check confirms that source control is available. Git records each project snapshot once you start tracking the code.

  • Verify Git by running this command:
git --version

What Does This Command Check?

The command asks Git to report its installed version. A version response confirms that Terminal can reach Git.

You should see the installed Git version. Your three local tools are now available.

Git Does Not Report a Version?

Confirm that Git is installed on your Mac. Reopen Terminal after any installation so the new command becomes available.

If Git still cannot run, help me diagnose my Git installation on macOS.

Create the Maven project

A Maven project keeps its build instructions in pom.xml. Its standard layout separates application code from test code.

  • Press Cmd+Space to open macOS search.
  • Type IntelliJ IDEA into the search bar.
  • Press Enter to open IntelliJ IDEA.
  • Click New Project on the welcome screen.
  • Enter resource-readiness-rules-engine in the Name field.
  • Set the project location to ~/Desktop/resource-readiness-rules-engine.
  • Select Maven in the Build system field.
  • Select JDK 17 in the JDK field.
  • Click Create to create the project.

IntelliJ IDEA opens the resource-readiness-rules-engine project. You can now see its files in the Project tool window.

The src/main/java folder holds application classes. The src/test/java folder holds automated tests.

  • Expand src/main/java in the Project tool window.
  • Right-click the java folder.
  • Select New from the menu.
  • Select Package from the submenu.
  • Enter com.nextwork.readiness as the package name.

The application package now appears below src/main/java. Its folder path is src/main/java/com/nextwork/readiness.

  • Expand src/test/java in the Project tool window.
  • Right-click the test java folder.
  • Select New from the menu.
  • Select Package from the submenu.
  • Enter com.nextwork.readiness as the package name.

The matching test package now appears below src/test/java. Its folder path is src/test/java/com/nextwork/readiness.

The Project Object Model records the project identity plus compiler settings. Maven reads these values each time it builds the project.

  • Select pom.xml in the Project tool window.
  • Replace its contents with this configuration:
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.nextwork</groupId>
    <artifactId>resource-readiness-rules-engine</artifactId>
    <version>1.0-SNAPSHOT</version>

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

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.16.0</version>
            </plugin>
        </plugins>
    </build>
</project>

What Does This Configuration Do?

  • The project coordinates identify the code as com.nextwork:resource-readiness-rules-engine:1.0-SNAPSHOT.
  • The maven.compiler.release property targets Java 17.
  • The Maven Compiler Plugin version 3.16.0 compiles the application sources.
  • Save pom.xml.
  • Click Terminal at the bottom of IntelliJ IDEA.
  • Compile the empty project structure by running this command:
mvn compile

What Does This Command Do?

Maven reads pom.xml before running the compile phase. It checks the project model plus prepares the target build output.

Maven confirms that the compile phase succeeded. That result proves the POM is valid.

Does Compilation Fail?

Confirm that the IntelliJ terminal is inside ~/Desktop/resource-readiness-rules-engine. Maven must find pom.xml in that folder.

Compare every XML closing tag with the full-file reference below. A missing closing tag prevents Maven from reading the project model.

If the compile phase still fails, help me diagnose my Maven compile error.

✔️ Awesome, I've Got Everything

Strong start. Your saved pom.xml now gives the project a repeatable Java 17 build.

ⓧ I'd Like to Double Check the Full Code

<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.nextwork</groupId>
    <artifactId>resource-readiness-rules-engine</artifactId>
    <version>1.0-SNAPSHOT</version>

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

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.16.0</version>
            </plugin>
        </plugins>
    </build>
</project>

What Should Match?

Confirm that your file matches this reference character for character. The project identity plus Java release plus compiler plugin must all remain inside the top-level project element.

Compile and verify the setup

The final check tests the whole toolchain from the IntelliJ terminal. Before you run it, which command do you expect to prove that the project configuration works?

  • Run the complete toolchain check from the IntelliJ terminal:
java -version
mvn -v
git --version
mvn compile

What Does This Final Check Prove?

  • The first command confirms that Java 17 or newer is active.
  • The second command confirms Maven 3.9.16 or another compatible 3.9 release.
  • The third command confirms that Git is available.
  • The final command confirms that Maven can compile the project model.

You see the Java version plus the Maven version plus the Git version. Maven then confirms that compilation succeeded.

Your repeatable build baseline is ready. Next, you will turn the empty structure into a working capacity decision.

Produce the First Readiness Result

The Maven project from the previous step now compiles with the verified toolchain. That stable baseline is ready for its first Java capacity check.

A visible capacity-only result gives you a working baby version before the design becomes more abstract. You will keep the comparison inside App so you can watch raw data become READY.

In this step, get ready to:
  • Model a resource's identity plus its available capacity.
  • Calculate readiness with a direct comparison inside App.
  • Run the application to explain its capacity-only result.
Define the resource data

A Resource object needs a name plus an available capacity for this decision. Encapsulation keeps those related values inside one object.

What Belongs in Resource?

  • The name field identifies the resource being evaluated.
  • The availableUnits field stores its current capacity.
  • The constructor receives the values needed to create a complete resource.
  • The getters provide controlled read access to the private fields.
  • In IntelliJ IDEA, select src/main/java/com/nextwork/readiness in the Project tool window.
  • Press Cmd+N to open the new-item menu.
  • Select Java Class.
  • Type Resource.
  • Press Enter.

You should see Resource.java inside src/main/java/com/nextwork/readiness.

  • Implement Resource.java from the contract above.
  • Open the matching tab when you are ready to compare your attempt.

✔️ Awesome, I've got everything!

Your Resource implementation is ready for the compile check below.

ⓧ I'd like to double check the full code

  • Compare Resource.java with this reference:
package com.nextwork.readiness;

public final class Resource {
    private final String name;
    private final int availableUnits;

    public Resource(String name, int availableUnits) {
        this.name = name;
        this.availableUnits = availableUnits;
    }

    public String getName() {
        return name;
    }

    public int getAvailableUnits() {
        return availableUnits;
    }
}

What Does This Class Do?

  • The name plus availableUnits fields preserve the resource data after construction.
  • The constructor stores each incoming value in its matching field.
  • The getters expose each value while the fields remain private.
  • Save Resource.java.
  • Return to the macOS Terminal from earlier.
  • Compile the application sources by running this command:
mvn compile

What Does This Command Do?

Maven reads pom.xml before compiling the application sources under src/main/java. This check confirms that Resource.java follows valid Java syntax.

Good progress. Maven now accepts Resource as part of the application.

Does Resource Fail to Compile?

  • Confirm that Resource.java sits inside src/main/java/com/nextwork/readiness.
  • Check that its package name is com.nextwork.readiness.
  • Compare the constructor parameter names with the field assignments.

Help me debug the Resource compilation.

Build the capacity decision

The main() method is the application's entry point. For this first version, App owns the capacity rule directly.

A local boolean comparison checks whether the available units meet the requirement. The stored result controls the readiness message.

Acceptance Criteria for App

  • The main() method creates a resource named ALPHA-1 with 8 available units.
  • The requiredUnits variable holds 6.
  • The ready variable stores the result of resource.getAvailableUnits() >= requiredUnits.
  • The output prints the resource name before the overall readiness result.
  • Return to IntelliJ IDEA.
  • Select src/main/java/com/nextwork/readiness in the Project tool window.
  • Press Cmd+N to open the new-item menu.
  • Select Java Class.
  • Type App.
  • Press Enter.

You should see App.java beside Resource.java in the package.

  • Implement App.java from the acceptance criteria above.
  • Open the matching tab when you are ready to compare your attempt.

✔️ Awesome, I've got everything!

Your App implementation is ready for its first run.

ⓧ I'd like to double check the full code

  • Compare App.java with this reference:
package com.nextwork.readiness;

public final class App {
    private App() {
    }

    public static void main(String[] args) {
        Resource resource = new Resource("ALPHA-1", 8);
        int requiredUnits = 6;
        boolean ready = resource.getAvailableUnits() >= requiredUnits;

        System.out.println("Resource: " + resource.getName());
        System.out.println("Overall readiness: " + (ready ? "READY" : "NOT READY"));
    }
}

What Does This Class Do?

  • The private constructor prevents the utility-style App class from being instantiated.
  • The main() method creates the sample resource used for this run.
  • The comparison checks whether 8 available units meet the requirement of 6.
  • The conditional expression turns the boolean result into a readable readiness label.
  • Save App.java.
Run and explain the first result

Before you run the application, do you expect 8 available units to satisfy a requirement of 6?

  • Return to the macOS Terminal from earlier.
  • Compile the project before launching the application by running these commands:
mvn compile
java -cp target/classes com.nextwork.readiness.App

What Do These Commands Do?

  • The first command refreshes the compiled application classes in target/classes.
  • The second command launches App with that directory on the classpath.

You will see Resource: ALPHA-1 followed by Overall readiness: READY.

You have your first visible result. The application now turns capacity data into a clear READY decision.

Is the READY Result Missing?

  • Confirm that Terminal is inside resource-readiness-rules-engine, where pom.xml lives.
  • Check that App.java creates ALPHA-1 with 8 available units.
  • Confirm that requiredUnits holds 6.

Help me trace the missing READY result.

  • Submit Resource.java plus App.java for review.
  • Explain how the constructor copies its arguments into the fields.
  • Explain why the getters expose the stored values.
  • Explain what resource represents.
  • Explain what requiredUnits represents.
  • Explain what ready stores.
  • Explain how >= produces the boolean result.

Your capacity-only version works. Next, a second requirement will test how much this compact design can explain.

Experience an Unexplained Failure

Your Maven project in IntelliJ IDEA already proves that ALPHA-1 has enough capacity. A new inspection requirement now belongs in the same readiness decision.

The fastest extension keeps both checks inside App.main(). You will run that version to judge how useful its output is when the inspection requirement fails.

In this step, get ready to:
  • Add inspection status to the resource model.
  • Run the combined readiness decision.
  • Explain what the overall result leaves unanswered.
Model the inspection status

A resource now carries capacity data plus inspection data. Encapsulation keeps those related values inside the same object.

The inspectionCurrent field uses a boolean value because the sample inspection is either current or out of date. Its getter gives the readiness decision controlled access to that state.

What Data Does Resource Need?

  • The existing name field identifies the resource.
  • The existing availableUnits field provides the capacity value.
  • The new inspectionCurrent field records whether the inspection requirement passes.
  • The constructor receives all three values so each Resource starts with complete readiness data.
Combine the readiness conditions

Boolean logic lets one result depend on multiple conditions. The && operator makes ready true only when capacity passes plus the inspection is current.

Your Implementation Criteria

  • Resource.java stores a private final boolean named inspectionCurrent.
  • Resource(String name, int availableUnits, boolean inspectionCurrent) saves all three constructor arguments.
  • isInspectionCurrent() returns the stored inspection status.
  • App.java creates ALPHA-1 with 8 available units plus an inspection value of false.
  • The ready variable combines the existing capacity comparison with resource.isInspectionCurrent().
  • The output remains limited to the resource name plus the overall readiness result.
  • Apply the inspection requirement across Resource.java plus App.java using the criteria above.
  • Save Resource.java plus App.java.
Run the unexplained result

The sample has enough capacity because 8 meets the requirement of 6. Its inspection status provides the second condition for this run.

  • Predict whether the report will name the failed requirement.
  • Compile and run the updated project by running these commands:
mvn compile
java -cp target/classes com.nextwork.readiness.App

What Do These Commands Do?

  • mvn compile compiles the application sources into target/classes.
  • java -cp target/classes com.nextwork.readiness.App launches the compiled App class.

The terminal prints Resource: ALPHA-1 followed by Overall readiness: NOT READY. It does not identify inspection as the failed requirement.

You made the second requirement count. The program reaches the correct decision while leaving the reason hidden.

Does the Project Fail to Compile or Show READY?

Confirm that the constructor call in App.java contains the same three arguments accepted by the constructor in Resource.java.

Check that the third constructor argument is false. Check that ready uses && before resource.isInspectionCurrent().

Help me diagnose my combined readiness result.

The combined boolean produces the correct overall answer. Anyone responding to that answer must inspect the code or data to infer which requirement failed.

Adding five more requirements would extend the same expression. Reporting code would also need direct knowledge of every new condition.

✔️ Awesome, I've got everything!

Great. Your two saved files now produce the intended NOT READY result without naming the failed inspection requirement.

ⓧ I'd like to double check the full code

  • Compare Resource.java with this complete reference:
package com.nextwork.readiness;

public final class Resource {
    private final String name;
    private final int availableUnits;
    private final boolean inspectionCurrent;

    public Resource(String name, int availableUnits, boolean inspectionCurrent) {
        this.name = name;
        this.availableUnits = availableUnits;
        this.inspectionCurrent = inspectionCurrent;
    }

    public String getName() {
        return name;
    }

    public int getAvailableUnits() {
        return availableUnits;
    }

    public boolean isInspectionCurrent() {
        return inspectionCurrent;
    }
}

How Does Resource Hold the New State?

  • The three private final fields hold the resource name, capacity, plus inspection status.
  • The constructor receives each value when the object is created.
  • The getters expose each value without allowing outside code to replace the stored state.
  • Compare App.java with this complete reference:
package com.nextwork.readiness;

public final class App {
    private App() {
    }

    public static void main(String[] args) {
        Resource resource = new Resource("ALPHA-1", 8, false);
        int requiredUnits = 6;
        boolean ready = resource.getAvailableUnits() >= requiredUnits
                && resource.isInspectionCurrent();

        System.out.println("Resource: " + resource.getName());
        System.out.println("Overall readiness: " + (ready ? "READY" : "NOT READY"));
    }
}

How Does App Reach Its Answer?

  • The local resource variable holds a sample with enough capacity plus an out-of-date inspection.
  • The local requiredUnits variable keeps the capacity threshold at 6.
  • The local ready variable becomes true only when both conditions pass.
  • The two print statements reveal the resource name plus one overall answer.

You have exposed the cost of one unexplained boolean result. Next, you will give each requirement its own rule so the report can name every outcome.

Make the Rules Explainable with an Interface

Your Maven project now reaches the correct overall answer for ALPHA-1. The single result still hides the failed inspection requirement.

A Java interface gives every rule the same contract. Independent rule objects can report their own names plus outcomes through that contract.

In this step, get ready to:
  • Define the shared contract for every readiness rule.
  • Implement independent rule classes plus an evaluator.
  • Refactor App.java to print an explainable readiness report.
Define the shared rule contract

Why Use an Interface?

Copying more if statements into App makes the entry point responsible for every requirement. Each new check expands the same decision block.

ReadinessRule creates a shared contract through name() plus passes(Resource resource). The evaluator can work with any object that implements those methods.

  • Use the Java file action in the IntelliJ IDEA Project sidebar to create ReadinessRule.java inside src/main/java/com/nextwork/readiness from the shared contract above.
  • Save ReadinessRule.java.
  • Check the contract by compiling the application sources with this command:
mvn compile

What Does This Command Check?

Maven runs its compile phase against the application sources. A successful compile confirms that the new contract uses valid Java syntax.

Good progress. The shared contract now compiles as part of your rules engine.

Does the Contract Fail to Compile?

Check that ReadinessRule.java starts with the same package declaration as Resource.java. Confirm that both method declarations end correctly.

Make sure passes(Resource resource) uses the existing Resource class name with matching capitalization.

Help me diagnose my readiness interface.

Build the rule implementations and evaluator

The contract becomes useful when different classes provide their own answers. Capacity uses a numeric comparison while inspection reads the status already stored in Resource.

Rule Acceptance Criteria

  • CapacityRule implements ReadinessRule with a constructor-supplied requiredUnits value.
  • CapacityRule.name() returns Capacity.
  • CapacityRule.passes(...) checks whether the available units meet the required units.
  • InspectionRule.name() returns Inspection.
  • InspectionRule.passes(...) returns the resource's current inspection status.
  • Create CapacityRule.java inside src/main/java/com/nextwork/readiness from the acceptance criteria.
  • Create InspectionRule.java in the same package from the acceptance criteria.
  • Check both implementations by compiling the application sources with this command:
mvn compile

What Does This Compile Prove?

Maven checks both concrete classes against the method signatures in ReadinessRule. A successful compile proves that both classes satisfy the shared contract.

You now have two independently compiled rules. Each one answers the same questions through its own implementation.

Do the Rule Classes Fail to Compile?

Confirm that both class declarations contain implements ReadinessRule. Check that each overridden method matches the interface signature exactly.

Check the capacity comparison against resource.getAvailableUnits(). Check the inspection rule against resource.isInspectionCurrent().

Help me diagnose my rule classes.

Evaluator Acceptance Criteria

ReadinessEvaluator receives a List<ReadinessRule> through its constructor. List.copyOf stores an unmodifiable copy of that collection.

isReady(Resource resource) stops when a rule fails. printReport(Resource resource) prints every rule outcome before calculating overall readiness.

  • Create ReadinessEvaluator.java inside src/main/java/com/nextwork/readiness from the evaluator acceptance criteria.
  • Save ReadinessEvaluator.java.
  • Check the evaluator by compiling the application sources with this command:
mvn compile

What Does This Compile Prove?

Maven now checks the evaluator against the shared rule type. A successful compile confirms that the constructor plus both evaluator methods agree with the existing classes.

That is the central design in place. One evaluator can now process every object that follows the rule contract.

Does the Evaluator Fail to Compile?

Confirm that ReadinessEvaluator.java imports java.util.List. Check that the field uses List<ReadinessRule>.

Compare every method call against the exact interface names. A mismatch in passes or name prevents the evaluator from compiling.

Help me diagnose my evaluator.

Refactor App and run the report

App now has one job for rule selection. It supplies the rules to the evaluator before asking for a report.

App Acceptance Criteria

  • App.java imports java.util.List.
  • The existing ALPHA-1 sample keeps 8 available units plus an inspection value of false.
  • List.of(...) contains CapacityRule(6) plus InspectionRule().
  • ReadinessEvaluator receives that rule list through its constructor.
  • evaluator.printReport(resource) replaces the direct boolean output.
  • Select App.java in the IntelliJ IDEA Project sidebar.
  • Refactor App.java against the acceptance criteria above.
  • Save App.java.
  • Compare every project file against the complete reference after finishing your attempt:

✔️ Awesome, I've got everything!

Great. Your rule contract, implementations, evaluator, plus entry point are ready for the build check.

ⓧ I'd like to double check the full code

<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.nextwork</groupId>
    <artifactId>resource-readiness-rules-engine</artifactId>
    <version>1.0-SNAPSHOT</version>

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

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.16.0</version>
            </plugin>
        </plugins>
    </build>
</project>

What Does the POM Control?

The POM keeps the Java release at 17. It also keeps Maven Compiler Plugin 3.16.0 in the build configuration.

package com.nextwork.readiness;

public final class Resource {
    private final String name;
    private final int availableUnits;
    private final boolean inspectionCurrent;

    public Resource(String name, int availableUnits, boolean inspectionCurrent) {
        this.name = name;
        this.availableUnits = availableUnits;
        this.inspectionCurrent = inspectionCurrent;
    }

    public String getName() {
        return name;
    }

    public int getAvailableUnits() {
        return availableUnits;
    }

    public boolean isInspectionCurrent() {
        return inspectionCurrent;
    }
}

What Does Resource Provide?

Resource keeps the name, available capacity, plus inspection status together. The rule objects read those values through its getters.

package com.nextwork.readiness;

public interface ReadinessRule {
    String name();

    boolean passes(Resource resource);
}

What Does the Contract Define?

ReadinessRule requires each implementation to provide a report name. It also requires a pass decision for a supplied resource.

package com.nextwork.readiness;

public final class CapacityRule implements ReadinessRule {
    private final int requiredUnits;

    public CapacityRule(int requiredUnits) {
        this.requiredUnits = requiredUnits;
    }

    @Override
    public String name() {
        return "Capacity";
    }

    @Override
    public boolean passes(Resource resource) {
        return resource.getAvailableUnits() >= requiredUnits;
    }
}

What Does the Capacity Rule Decide?

CapacityRule stores the required capacity supplied by App. Its comparison passes when the resource has enough available units.

package com.nextwork.readiness;

public final class InspectionRule implements ReadinessRule {
    @Override
    public String name() {
        return "Inspection";
    }

    @Override
    public boolean passes(Resource resource) {
        return resource.isInspectionCurrent();
    }
}

What Does the Inspection Rule Decide?

InspectionRule reads the inspection status from the resource. A current inspection passes this rule.

package com.nextwork.readiness;

import java.util.List;

public final class ReadinessEvaluator {
    private final List<ReadinessRule> rules;

    public ReadinessEvaluator(List<ReadinessRule> rules) {
        this.rules = List.copyOf(rules);
    }

    public boolean isReady(Resource resource) {
        for (ReadinessRule rule : rules) {
            if (!rule.passes(resource)) {
                return false;
            }
        }
        return true;
    }

    public void printReport(Resource resource) {
        System.out.println("Resource: " + resource.getName());

        for (ReadinessRule rule : rules) {
            String result = rule.passes(resource) ? "PASS" : "FAIL";
            System.out.println(rule.name() + ": " + result);
        }

        String overall = isReady(resource) ? "READY" : "NOT READY";
        System.out.println("Overall readiness: " + overall);
    }
}

What Does the Evaluator Do?

ReadinessEvaluator stores the supplied rules in an unmodifiable list. It checks readiness plus prints each named outcome without depending on concrete rule classes.

package com.nextwork.readiness;

import java.util.List;

public final class App {
    private App() {
    }

    public static void main(String[] args) {
        Resource resource = new Resource("ALPHA-1", 8, false);

        List<ReadinessRule> rules = List.of(
                new CapacityRule(6),
                new InspectionRule()
        );

        ReadinessEvaluator evaluator = new ReadinessEvaluator(rules);
        evaluator.printReport(resource);
    }
}

What Does App Configure?

App selects the rules for this run. It passes those rules to the evaluator before requesting the report.

  • Return to the macOS Terminal from earlier.
  • Compile the completed refactor by running this command:
mvn compile

What Does the Final Compile Check?

Maven compiles the complete set of application classes. Success confirms that App can create each rule plus pass the shared rule list to the evaluator.

The full refactor now compiles. Your original combined condition has become a set of cooperating rule objects.

Does the Refactor Fail to Compile?

Compare each Java filename plus package declaration with the full-code reference. Confirm that App.java imports java.util.List.

Check every constructor call against its class definition. Pay close attention to CapacityRule(6) plus ReadinessEvaluator(rules).

Help me diagnose the completed refactor.

Before you run the application, which requirement do you expect to pass? Hold that prediction while you test the report.

  • Run the compiled application with this command:
java -cp target/classes com.nextwork.readiness.App

What Does This Command Launch?

The -cp option points Java at the compiled classes in target/classes. Java then launches the main method in com.nextwork.readiness.App.

You will see Capacity: PASS. You will also see Inspection: FAIL plus Overall readiness: NOT READY.

How Does the Design Work?

  • CapacityRule plus InspectionRule implement the same interface through different method bodies.
  • Constructor injection gives the evaluator its rules when App creates it.
  • List.copyOf protects the evaluator from later changes to the supplied collection.
  • The enhanced for loop visits each rule through the shared ReadinessRule type.
  • Polymorphism lets the evaluator call the correct implementation without checking the concrete rule class.
  • The local variables result plus overall turn boolean decisions into readable report labels.
  • Arrange IntelliJ IDEA so the Project sidebar shows the new rule files plus App.java.
  • Keep the macOS Terminal visible with the completed readiness report.
  • Capture the refactor plus report for the checkpoint below.

Your rules now explain exactly why a resource fails. Next, you will protect both readiness outcomes with JUnit tests.

Protect the Rules with JUnit

Your evaluator now reports the result of every readiness rule. That clear design gives you a stable contract to protect as the engine grows.

A single manual run proves only one sample. JUnit checks both readiness outcomes automatically. The Maven Surefire Plugin runs those checks through the Maven test phase.

In this step, get ready to:
  • Configure JUnit plus Maven Surefire in the POM.
  • Write tests for both readiness outcomes.
  • Run the test suite through Maven.
Configure JUnit and Maven Surefire

The JUnit BOM keeps version 6.1.3 consistent across JUnit modules. The JUnit Jupiter dependency supplies the test API plus the test engine.

  • Select pom.xml in the IntelliJ IDEA project from earlier.
  • Add the JUnit dependency configuration between the closing </properties> tag and the opening <build> tag by copying this block:
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.junit</groupId>
                <artifactId>junit-bom</artifactId>
                <version>6.1.3</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

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

What Does This Configuration Add?

  • The junit-bom entry centralizes the JUnit version at 6.1.3.
  • The junit-jupiter dependency provides the tools that define plus run your tests.
  • The test scope keeps JUnit out of the application runtime.
  • Save pom.xml.
  • Confirm that Maven accepts the dependency configuration by running:
mvn test

What Does This Command Check?

The Maven test phase reads the updated POM. It resolves the test dependencies before completing the build lifecycle.

Maven finishes with BUILD SUCCESS. Your JUnit dependency configuration is valid.

Does Maven Reject the POM?

Confirm that both dependency sections sit after </properties>. Confirm that they appear before <build>.

Check that each opening XML tag has a matching closing tag.

Help me diagnose my JUnit dependency configuration.

Maven can now resolve JUnit. Surefire gives the test phase an explicit runner version for repeatable test execution.

  • Find the closing </plugin> tag for maven-compiler-plugin in pom.xml.
  • Add the Maven Surefire Plugin directly below the compiler plugin by copying this block:
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.6.0</version>
            </plugin>

What Does Surefire Contribute?

Maven Surefire 3.6.0 discovers the JUnit tests during the test phase. It reports each test outcome as part of the Maven build.

  • Save pom.xml.
  • Confirm that Maven can load the pinned test runner by running:
mvn test

What Does This Run Prove?

Maven loads Surefire 3.6.0 during the test phase. A successful build proves that the test tooling is ready for your test class.

Maven finishes with BUILD SUCCESS. The project is ready to discover JUnit tests.

Does Surefire Fail to Load?

Confirm that the Surefire plugin sits inside <plugins>. Confirm that it is separate from the compiler plugin.

Check that the Surefire version is 3.6.0.

Help me fix my Maven Surefire configuration.

✔️ Awesome, I've got everything!

Your POM now includes JUnit dependency management plus the pinned Maven test runner.

ⓧ I'd like to double check the full code

<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.nextwork</groupId>
    <artifactId>resource-readiness-rules-engine</artifactId>
    <version>1.0-SNAPSHOT</version>

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

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.junit</groupId>
                <artifactId>junit-bom</artifactId>
                <version>6.1.3</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

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

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.16.0</version>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.6.0</version>
            </plugin>
        </plugins>
    </build>
</project>

How Does the POM Fit Together?

The dependency sections provide JUnit only to test code. The build section keeps Java compilation plus test execution on explicit plugin versions.

Write both readiness tests

Each test follows an arrange, act, assert flow. You create a resource with known values before calling isReady() and checking the returned boolean.

Your Test Contract

  • The class owns one ReadinessEvaluator configured with CapacityRule(6) plus InspectionRule().
  • The resourceIsReadyWhenEveryRulePasses() method creates a resource with eight available units plus a current inspection.
  • The resourceIsNotReadyWhenInspectionFails() method creates a resource with eight available units plus an expired inspection.
  • Each method uses @Test with either assertTrue or assertFalse against ReadinessEvaluator.isReady.
  • Create ReadinessEvaluatorTest.java inside src/test/java/com/nextwork/readiness with IntelliJ IDEA's Java class creation control using the contract above.

✔️ Awesome, I've got everything!

Your test class now covers a fully ready resource plus a failed inspection.

ⓧ I'd like to double check the full code

package com.nextwork.readiness;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.List;

import org.junit.jupiter.api.Test;

class ReadinessEvaluatorTest {
    private final ReadinessEvaluator evaluator = new ReadinessEvaluator(
            List.of(new CapacityRule(6), new InspectionRule())
    );

    @Test
    void resourceIsReadyWhenEveryRulePasses() {
        Resource resource = new Resource("ALPHA-1", 8, true);

        assertTrue(evaluator.isReady(resource));
    }

    @Test
    void resourceIsNotReadyWhenInspectionFails() {
        Resource resource = new Resource("ALPHA-1", 8, false);

        assertFalse(evaluator.isReady(resource));
    }
}

How Is the Test Class Structured?

  • The shared evaluator uses the same two rule types as the application.
  • The first resource satisfies both rules. Its assertion expects isReady() to return true.
  • The second resource fails inspection. Its assertion expects isReady() to return false.
  • Save ReadinessEvaluatorTest.java.
Run and explain the suite

Before you run the suite, predict which boolean result each resource should produce.

  • Run both tests from the macOS Terminal from earlier with this command:
mvn test

What Does Maven Test Do?

  • Maven compiles the application source plus the test source.
  • Surefire discovers the methods marked with @Test.
  • JUnit executes each assertion. A failed expectation makes the Maven build fail.

You see two tests run with zero failures. Maven finishes with BUILD SUCCESS.

Do the Tests Fail?

Confirm that ReadinessEvaluatorTest.java uses the package com.nextwork.readiness.

Check the inspection value in each resource. The passing sample uses true. The failing sample uses false.

Help me diagnose my readiness evaluator tests.

Where Are Arrange, Act, and Assert?

  • Arrange creates the shared evaluator plus a resource with controlled values.
  • Act calls isReady() with that resource.
  • Assert uses assertTrue or assertFalse to protect the expected result.
  • Maven Surefire connects these JUnit checks to the repeatable mvn test command.

That is the rules engine protected. Both the ready path plus the failed-inspection path now have repeatable checks.

Secret mission

Run the Tests in GitHub Actions

Publish your rules engine in a public GitHub repository. Add a GitHub Actions workflow that runs your Maven tests automatically on every push or pull request to main.

Clean Up Your Resources

Clean Up Your Resources

Choose whether to keep the project active, pause its automated workflow, or remove both copies. Standard GitHub-hosted runners are free and unlimited for public repositories.

Resources you used:

  • The local Maven project in the resource-readiness-rules-engine folder. It includes the generated target output.
  • The public GitHub repository named resource-readiness-rules-engine. It holds the GitHub Actions Java CI workflow plus the Maven dependency cache.

Keep everything running

No action is needed. Choose this option if you want Java CI to keep testing future changes.

  • Keep the local resource-readiness-rules-engine folder for future development.
  • Leave the public repository available for future pushes.
  • Leave Java CI enabled to test each future push to main.

Pause - I'll come back to this later

Pause the automated workflow while keeping the local folder and public repository. Your project files remain unchanged.

  • Return to the public resource-readiness-rules-engine repository on GitHub.
  • Select the Actions tab.
  • Select Java CI from the workflow list.
  • Open the workflow options menu.
  • Choose Disable workflow.

The workflow page now offers Enable workflow. This confirms that Java CI is paused.

Choosing Enable workflow later resumes automatic test runs.

Delete - I don't want to use this again

Remove all project resources to start fresh. Deletion is permanent, so use this option only when you no longer need either copy.

  • Close IntelliJ IDEA.
  • Use Finder to locate the resource-readiness-rules-engine folder where you created it.
  • Use Finder to delete the resource-readiness-rules-engine folder.

The folder no longer appears in Finder. This confirms that the Java source files and generated build output are removed.

  • Return to the public resource-readiness-rules-engine repository on GitHub.
  • Select the Settings tab.
  • Scroll to Danger Zone.
  • Select Delete this repository.
  • Complete each warning acknowledgement.
  • Type resource-readiness-rules-engine when GitHub asks you to confirm the repository name.
  • Confirm the deletion by selecting Delete this repository again.

The repository URL no longer opens your project. This confirms that the remote repository and Java CI workflow are removed.

Nice Work!

Nice Work!

You did it! Your Java rules engine now explains each readiness decision with per-rule results. You also proved its ready and not-ready behavior with automated tests.

You've learned how to:

  • Build an explainable readiness evaluator that reports each rule as PASS or FAIL before showing the overall result.
  • Replace one growing boolean expression with independent rules behind a Java interface. Use polymorphism so ReadinessEvaluator can process every rule consistently.
  • Protect both ready and not-ready behavior with repeatable JUnit tests. Run the suite through Maven to catch regressions.
  • Secret Mission: Publish the resource-readiness-rules-engine repository publicly on GitHub. Automate mvn test on pushes and pull requests to main with GitHub Actions.

Ready to quiz yourself?