mattone
dopo mattoneTHE BOOK SERIES
IT/EN
← Java guide

Java 25 · 37/39

Guided activities for C29 and C30

Java 25 · Complete guide · Draft under review

This guide retains the book’s draft status. Editorial review and comprehension checks with independent readers remain to be completed.

Search the whole guide →

These activities use the chapter 29 catalog and chapter 30 programs. Every change starts from an observable defect: first formulate a prediction, then change one thing and check the result. Commands producing files write into target/ or a local build/ folder. C29 requires JDK 25 and Maven with access to the dependencies declared in the POM; C30 needs only JDK 25.

C29.1 – The JAR exists, but does not start

A colleague receives catalog-1.0.0.jar. jar --list --file target/catalog-1.0.0.jar shows the classes, while java -jar reports a missing main manifest attribute. Before modifying Java code, identify the boundary that failed. Rebuild a JAR version without the mainClass configuration, observe the problem, restore the POM and run mvn clean verify and the JAR with data/titles.txt.

Success criterion. Readers can identify META-INF/MANIFEST.MF, show the entry Main-Class: example.catalog.CatalogCli in the correct JAR and obtain two titles in output. They can explain why the presence of CatalogCli.class is insufficient for java -jar to choose an entry point.

Hint. Compare jar --describe-module --file ... only if studying modules; for this exercise, open the manifest with unzip -p target/catalog-1.0.0.jar META-INF/MANIFEST.MF or an archive program. The application uses the class path.

Reasoned solution. java -jar reads the manifest. The POM's JAR plugin writes Main-Class because the <mainClass> configuration requests it. Without that entry, the JAR may contain compiled classes yet lack a chosen entry for this launch mode. The repair belongs to the build; changing main does not solve the artifact's defect.

C29.2 – A green test does not cover the boundary

The catalog accepts a title of eighty char units and rejects one of eighty-one. Add a test for the first case and verify the second. Before executing it, write which result you expect for "x".repeat(80) and "x".repeat(81). Temporarily change the condition in Titles.normalize from > 80 to >= 80: the new test must turn red. Restore the code and repeat mvn clean verify.

Success criterion. The Surefire report shows the new test; the >= 80 mutation is discovered; restored code returns to green. Readers can say that a coverage percentage, even high, would not alone have expressed this boundary.

Hint. Use assertEquals(80, Titles.normalize("x".repeat(80)).length()) and retain assertThrows for the 81-unit input. Do not modify the test to accommodate the introduced defect.

Reasoned solution. The rule includes the maximum: length() > 80 rejects only greater lengths. The test at 80 verifies the accepted side, the one at 81 the rejected side. A mutation moving the boundary must fail at least the first. This is more informative than a test merely executing the method.

C29.3 – Who uses JUnit when the application starts?

Examine pom.xml, then run mvn dependency:tree if the plugin is available in your environment. Identify JUnit and any transitive dependencies. Produce the JAR, check that it contains no org/junit classes, and run the application. State the consequence of replacing <scope>test</scope> with default behavior, without claiming Maven will automatically copy all libraries into the ordinary JAR.

Success criterion. Readers distinguish test dependency, runtime dependency and physical JAR contents. They recognize that the JAR plugin used here does not create a fat JAR.

Hint. jar --list --file target/catalog-1.0.0.jar shows actual contents. dependency:tree instead shows the graph resolved by the build: they are different observations.

Reasoned solution. JUnit is necessary to compile and execute tests. Test scope excludes it from the application's normal class path. Changing scope may expand the application's dependency graph, but does not by itself transform an ordinary JAR into an archive incorporating external JARs. The program works because its three production classes use only JDK APIs.

C29.4 – From the JAR to another environment

Run jdeps --print-module-deps target/catalog-1.0.0.jar; create a runtime image with jlink --add-modules java.base --output target/runtime-catalog and launch the JAR with target/runtime-catalog/bin/java -jar .... Then add, on a branch or in a test copy, a class directly using jdk.jfr.Recording. Repeat analysis and explain why a runtime built with only java.base no longer satisfies the new program. Restore the chapter's project.

Success criterion. Readers show jdeps output before and after modification, connect jdk.jfr to the new API and do not mistake runtime size for functional evidence. The transfer can be explained without producing the second package too.

Hint. jdeps analyzes static dependencies. Reflection or dynamic loading may require further runtime checks.

Reasoned solution. The initial application uses java.base APIs; that module therefore suffices for the tested runtime. The added class imports jdk.jfr.Recording, and the static graph must include jdk.jfr. Analysis suggests modules for jlink, but launching and use cases remain necessary tests, especially when the program loads components dynamically.

C30.1 – The limit is behavior, rather than a comment

Compile ControlledImport.java and TestControlledImport.java with javac --release 25 -Xlint:all -d build ... in the C30 folder. Add two tests: a line of exactly 80 char units must be accepted; an archive with exactly 100 nonempty lines must be accepted. Retain the existing 81 and 101 cases, which must be rejected. Explain what happens if the final character sequence is \r\n.

Success criterion. Four sides of the two boundaries have an explicit expected result. Readers can locate the check before line accumulation and before adding title number 101.

Hint. "x".repeat(80) and "x\n".repeat(100) build inputs without external files. The parser ignores \r and uses \n as line end; this is a teaching-format choice, to document if data has other conventions.

Reasoned solution. When line.length() is 80, the next non-line-ending character causes IOException; an 80-unit line is accepted, an 81-unit one is not. When the list contains 100 titles, the next call to add fails. Reading \r\n does not add \r to the title. The test verifies the concrete contract, rather than a vague promise of “controlled input”.

Variant. Try sending many blank lines. The hundred-title limit does not stop this reading: decide an overall character or line maximum, add a distinct error and a test crossing it. Perform the new check during reading, rather than after completing it.

C30.2 – A user-supplied path

Imagine exposing the catalog as a service: the client sends the filename to import. Design an authorized directory and write two test cases, one for an internal file and one for a path attempting to leave with ... Add a third case with a symbolic link pointing outside the directory. Describe where the check belongs and which risk remains if the file system changes between checking and opening.

Success criterion. The solution does more than search for the substring ..; it considers normalization, symbolic links, authorization and file opening. It also distinguishes the parser's line limit from permission to access that file.

Hint. Start from the root directory chosen by the server, rather than the process's current directory. Path, Files and file system attributes help reason about the actual path; some systems need an opening strategy reducing the window between checking and use.

Reasoned solution. The client must not freely choose any path available to the process. The solution defines a root, resolves and verifies the candidate against it, and treats symbolic links under an explicit policy. Checking the name and the actually opened object are not identical: an attacker with concurrent file system access may change the object after a separate check. The service design must choose APIs and permissions suitable for the operating system, then test permitted and rejected paths.

C30.3 – Logs and recordings tell different facts

Run ImportDiagnostics and print events with jfr print --events example.catalog.Import build/catalog.jfr. Compare the logger's message Imported 2 titles with a JFR event. For each, record the question it answers and one it does not answer. Propose a request identifier connecting an import error to service logs without writing received titles.

Success criterion. Three events are visible, each with titles = 2; durations are treated as local observations. Readers avoid putting input, tokens or personal data in diagnostic fields for convenience.

Hint. The log says a call finished with a count. The JFR event, if enabled during the right period, also retains a time interval. Neither proves that input from a different request would be accepted.

Reasoned solution. The log is an application message about a result. The JFR event connects operation type, count, thread and an observed duration. An opaque identifier generated at the request boundary may appear in entry, error and exit logs; it must have a retention policy and must not reveal file content. Investigating intermittent slowness needs measurements during the problem period, rather than three numbers from the example.

C30.4 – Transferring the model to HTTP and a database

The catalog now downloads titles from an HTTP response and saves them in a database. Write an operational contract for a response interrupted halfway through and a database failing after 40 inserts. Identify where you limit response size and time, when you validate records, how you avoid partial state and what you observe during another attempt. No framework need be chosen: the test concerns decisions and boundaries.

Success criterion. The solution connects a problem to each measure: network limits against endless responses; validation against malformed data; a transaction or equivalent strategy against partial imports; idempotency against retry duplicates; logs and metrics to know the outcome without exposing data. Readers identify at least one test causing each failure.

Hint. The parser's list may be valid and saving may still fail. Ask when the new catalog must become visible to users.

Reasoned solution. Bound the response with timeouts and a maximum of bytes or records. Verify the format while reading and reject the entire import if policy requires a complete set. Saving occurs in a transaction, or in a new version replaced only at the end: failure after 40 lines thus does not expose an incomplete catalog. An import identifier or natural key allows deciding a retry's effect. Tests with a truncated response, incorrect record and database failure demonstrate behavior; logs show phase, outcome and identifier without copying titles.

Try the examples

Running the programs requires JDK 25. Download the individual Java files linked in the chapter or the complete example project, which includes instructions and a launcher script. The explanations also compare expected output: predict it before running the program.

Massimiliano Tarquini · CC BY-NC 4.0

Back to the top ↑