Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: CI

on:
push:
branches: [ master, main ]
pull_request:
branches: [ master, main ]

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Set up JDK 11
uses: actions/setup-java@v4
with:
java-version: '11'
distribution: 'temurin'
cache: maven

- name: Compile
run: mvn -B clean compile

- name: Test
run: mvn -B test
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
/target
/local
dependency-reduced-pom.xml

# Eclipse, Netbeans and IntelliJ files
/.*
!.gitignore
!.github
/nbproject
/*.ipr
/*.iws
Expand Down
58 changes: 58 additions & 0 deletions MIGRATION_NOTES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Migration Notes: Java 9 → Java 11

## Java Version Change

- **From**: Java 9 (source/target 1.9)
- **To**: Java 11 (using `<release>11</release>`)
- The `<release>` flag replaces the legacy `<source>`/`<target>` configuration and provides stronger cross-compilation guarantees.

## Plugin Upgrades

| Plugin | Old Version | New Version | Notes |
|--------|-------------|-------------|-------|
| maven-compiler-plugin | 3.1 | 3.11.0 | Switched from source/target to release flag |
| maven-surefire-plugin | (none) | 3.2.5 | Added for test execution |
| maven-failsafe-plugin | (none) | 3.2.5 | Added for integration test support |
| maven-enforcer-plugin | (none) | 3.5.0 | Added to enforce Java 11+ at build time |
| maven-javadoc-plugin | (none) | 3.6.3 | Added with `<doclint>none</doclint>` (Java 11 doclint is stricter) |
| maven-shade-plugin | (unversioned) | 3.6.2 | Pinned explicit version (was unversioned, which Maven warns is unstable) |

Build encoding is pinned to UTF-8 for both source (`project.build.sourceEncoding`) and reporting (`project.reporting.outputEncoding`).

## Dependency Upgrades

| Dependency | Old Version | New Version | Notes |
|------------|-------------|-------------|-------|
| JUnit | 4.11 | 4.13.2 | Latest JUnit 4.x; added `<scope>test</scope>` |
| JMH (jmh-core) | 1.17.4 | 1.37 | Java 11 compatible |
| JMH (jmh-generator-annprocess) | 1.17.4 | 1.37 | Java 11 compatible |

## Code Fixes Required

Minimal changes were needed:

- **Moved `ReadPositiveIntParam.java`** from `src/main/java` to `src/test/java` — this file contained JUnit `@Test` annotations but was incorrectly placed in the main source tree. With JUnit correctly scoped to `test`, it must reside under `src/test/java`.
- No Nashorn JavaScript engine references found
- No internal `sun.*` or `com.sun.*` API usage
- No Java EE modules (removed in Java 11) were used
- Deprecation warning in `Util.java` (deprecated API usage) — informational only, no breaking change

## New Test Coverage

Added integration tests covering core chapters:

| Test File | Chapter | Coverage |
|-----------|---------|----------|
| `FilteringApplesTest.java` | Chapter 1 | Lambda expressions, method references, Predicate-based filtering |
| `StreamBasicsTest.java` | Chapter 4 | Stream creation, filtering, sorting, Java 7 vs Java 8 comparison |
| `StreamProcessingTest.java` | Chapter 5 | Trader/Transaction queries, reduce, distinct, anyMatch |
| `OptionalTest.java` | Chapter 10 | Optional creation, map, flatMap, orElse, filter, stream() |
| `CompletableFutureTest.java` | Chapter 11 | supplyAsync, thenApply, thenCombine, thenCompose, exceptionally |
| `DateTimeTest.java` | Chapter 12 | LocalDate, LocalTime, LocalDateTime, Duration, TemporalAdjusters, DateTimeFormatter |

## CI Workflow Added

- GitHub Actions workflow (`.github/workflows/ci.yml`)
- Triggers on push/PR to master/main branches
- Uses Temurin JDK 11 with Maven caching
- Runs `mvn -B clean compile` and `mvn -B test`
35 changes: 19 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,31 +29,34 @@ The source code for all examples can be found in the directory [src/main/java/la
* Appendix D: Lambdas and JVM bytecode
We will update the repository as we update the book. Stay tuned!

### Make sure to have JDK8 installed
The latest binary can be found here: http://www.oracle.com/technetwork/java/javase/overview/java8-2100321.html
### Prerequisites

$ java -version

java version "1.8.0_05"
Java(TM) SE Runtime Environment (build 1.8.0_05-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.5-b02, mixed mode)
**JDK 11** or higher is required. You can use any distribution (Temurin, OpenJDK, Oracle JDK, etc.).

```
$ java -version
openjdk version "11.0.x" ...
```

You can download a preview version here: https://jdk8.java.net/
Download Temurin JDK 11: https://adoptium.net/temurin/releases/?version=11

### Compile/Run the examples
Using maven:

$ mvn compile
Using Maven:

```
$ mvn clean compile
$ cd target/classes

$ java lambdasinaction/chap1/FilteringApples
```

Run tests:

Alternatively you can compile the files manually inside the directory src/main/java
```
$ mvn test
```

You can also import the project in your favorite IDE:
* In IntelliJ use "File->Open" menu and navigate to the folder where the project resides
* In Eclipse use "File->Import->Existing Maven Projects" (also modify "Reduntant super interfaces" to report as Warnings instead of Errors
* In Netbeans use "File->Open Project" menu
Alternatively you can import the project in your favorite IDE:
* In IntelliJ use "File->Open" menu and navigate to the folder where the project resides
* In Eclipse use "File->Import->Existing Maven Projects"
* In Netbeans use "File->Open Project" menu
56 changes: 49 additions & 7 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,26 @@

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.release>11</maven.compiler.release>
</properties>

<dependencies>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.17.4</version>
<version>1.37</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.17.4</version>
<version>1.37</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>

Expand All @@ -35,15 +38,54 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<version>3.11.0</version>
<configuration>
<source>1.9</source>
<target>1.9</target>
<release>11</release>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
</plugin>
Comment on lines +46 to +50

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Surefire skips the moved JUnit class because its name does not match test includes

The PR moves ReadPositiveIntParam under src/test/java, but the class still keeps the name ReadPositiveIntParam and its JUnit method is at src/test/java/lambdasinaction/chap10/ReadPositiveIntParam.java:10-13. Surefire’s default includes only run classes matching patterns such as Test*, *Test, *Tests, or *TestCase; the newly added Surefire configuration does not add an include for this class, so mvn test/CI will silently skip the migrated test even though the migration notes say it was moved because it contains JUnit tests.

Prompt for agents
The migrated JUnit class src/test/java/lambdasinaction/chap10/ReadPositiveIntParam.java is not discovered by Maven Surefire because its class name does not match the default test include patterns. Either rename the class and file to a Surefire-discoverable name such as ReadPositiveIntParamTest, or configure the maven-surefire-plugin in pom.xml to include this specific test class. Ensure mvn test actually executes its testMap method.
Open in Devin Review (Staging)

Was this helpful? React with 👍 or 👎 to provide feedback.

Debug

Playground

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>3.2.5</version>
</plugin>
Comment on lines +51 to +55

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 Added failsafe plugin does not run integration tests by itself

The new Failsafe plugin is declared but has no executions bound to integration-test/verify, and the CI workflow only runs mvn -B test. That is not a bug for the current PR because all added tests are regular Surefire-style JUnit classes under src/test/java and their names end in Test; however, the migration notes' phrase "integration test support" may be misleading unless future work binds Failsafe goals or CI runs mvn verify.

Open in Devin Review (Staging)

Was this helpful? React with 👍 or 👎 to provide feedback.

Debug

Playground

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.5.0</version>
<executions>
<execution>
<id>enforce-java</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<requireJavaVersion>
<version>[11,)</version>
<message>Java 11 or higher is required to build this project.</message>
</requireJavaVersion>
</rules>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.6.3</version>
<configuration>
<doclint>none</doclint>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.2</version>
<executions>
<execution>
<phase>package</phase>
Expand All @@ -63,4 +105,4 @@
</plugin>
</plugins>
</build>
</project>
</project>
64 changes: 64 additions & 0 deletions src/test/java/lambdasinaction/chap1/FilteringApplesTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package lambdasinaction.chap1;

import lambdasinaction.chap1.FilteringApples.Apple;
import org.junit.Test;

import java.util.Arrays;
import java.util.List;

import static org.junit.Assert.*;

public class FilteringApplesTest {

private final List<Apple> inventory = Arrays.asList(
new Apple(80, "green"),
new Apple(155, "green"),
new Apple(120, "red"));

@Test
public void filterGreenApples() {
List<Apple> result = FilteringApples.filterGreenApples(inventory);
assertEquals(2, result.size());
assertTrue(result.stream().allMatch(a -> "green".equals(a.getColor())));
}

@Test
public void filterHeavyApples() {
List<Apple> result = FilteringApples.filterHeavyApples(inventory);
assertEquals(1, result.size());
assertTrue(result.get(0).getWeight() > 150);
}

@Test
public void filterWithMethodReference() {
List<Apple> greens = FilteringApples.filterApples(inventory, FilteringApples::isGreenApple);
assertEquals(2, greens.size());

List<Apple> heavies = FilteringApples.filterApples(inventory, FilteringApples::isHeavyApple);
assertEquals(1, heavies.size());
}

@Test
public void filterWithLambda() {
List<Apple> greens = FilteringApples.filterApples(inventory, a -> "green".equals(a.getColor()));
assertEquals(2, greens.size());

List<Apple> heavies = FilteringApples.filterApples(inventory, a -> a.getWeight() > 150);
assertEquals(1, heavies.size());
}

@Test
public void filterWithComplexPredicate() {
List<Apple> result = FilteringApples.filterApples(inventory,
a -> a.getWeight() < 80 || "brown".equals(a.getColor()));
assertTrue(result.isEmpty());
}

@Test
public void appleToString() {
Apple apple = new Apple(80, "green");
String str = apple.toString();
assertTrue(str.contains("green"));
assertTrue(str.contains("80"));
}
}
84 changes: 84 additions & 0 deletions src/test/java/lambdasinaction/chap10/OptionalTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package lambdasinaction.chap10;

import org.junit.Test;

import java.util.Arrays;
import java.util.Optional;
import java.util.Set;

import static org.junit.Assert.*;

public class OptionalTest {

@Test
public void getCarInsuranceNameWithEmptyPerson() {
OptionalMain main = new OptionalMain();
String result = main.getCarInsuranceName(Optional.empty());
assertEquals("Unknown", result);
}

@Test
public void optionalOfNullable() {
Optional<String> opt = Optional.ofNullable(null);
assertFalse(opt.isPresent());

Optional<String> present = Optional.ofNullable("hello");
assertTrue(present.isPresent());
assertEquals("hello", present.get());
}

@Test
public void optionalMap() {
Optional<String> name = Optional.of("Java");
Optional<Integer> len = name.map(String::length);
assertEquals(Integer.valueOf(4), len.get());
}

@Test
public void optionalFlatMap() {
Optional<Optional<String>> nested = Optional.of(Optional.of("nested"));
Optional<String> flat = nested.flatMap(x -> x);
assertEquals("nested", flat.get());
}

@Test
public void optionalOrElse() {
Optional<String> empty = Optional.empty();
assertEquals("default", empty.orElse("default"));

Optional<String> present = Optional.of("value");
assertEquals("value", present.orElse("default"));
}

@Test
public void optionalFilter() {
Optional<String> opt = Optional.of("hello");
assertTrue(opt.filter(s -> s.startsWith("h")).isPresent());
assertFalse(opt.filter(s -> s.startsWith("z")).isPresent());
}

@Test
public void optionalIfPresent() {
Optional<String> opt = Optional.of("test");
StringBuilder sb = new StringBuilder();
opt.ifPresent(sb::append);
assertEquals("test", sb.toString());
}

@Test
public void optionalStream() {
Optional<String> present = Optional.of("value");
long count = present.stream().count();
assertEquals(1, count);

Optional<String> empty = Optional.empty();
long emptyCount = empty.stream().count();
assertEquals(0, emptyCount);
}

@Test
public void insuranceNameAccess() {
Insurance insurance = new Insurance();
assertNull(insurance.getName());
}
}
Loading
Loading