Java Platform Module System (JPMS) practice questions

From OCP Java SE 21 (1Z0-830) · 18 questions on this topic

Java Platform Module System (JPMS) practice questions from OCP Java SE 21 (1Z0-830). This pack has 18 questions tagged Java Platform Module System (JPMS), drawn from its timed mock exams. 8 of them are worked through in full below — the question, every option, why each is right or wrong, and the explanation.

Worked examples for Java Platform Module System (JPMS)

  1. Question 1

    A colleague hands you a compiled modular JAR for module com.store in the mods directory, but no source. Before you write any code against it, you want to see which packages it exports and which services it provides — without launching the application. Which command prints that module descriptor?

    1. A. java --module-path mods --list-modules com.store

      --list-modules enumerates the observable modules and their versions but prints no directives, so it cannot reveal which packages a module exports or which services it provides.

    2. B. java --module-path mods --describe-module com.storeCorrect answer

      java --describe-module resolves the named module on the module path and prints its full descriptor — requires, exports, opens, uses and provides — then exits without launching the application.

    3. C. jdeps --module-path mods --list-deps com.store

      jdeps --list-deps reports what the module depends ON, the opposite direction, so it does not print the module's own exports and provides.

    4. D. jlink --module-path mods --describe com.store

      jlink assembles custom runtime images and has no --describe option, so this command cannot print a module descriptor.

    Explanation

    java --describe-module <module> (short form -d) resolves the named module on the module path and prints its descriptor: requires, exports, opens, uses and provides. --list-modules is the near miss: it enumerates the observable modules and their versions, but prints no directives, so it cannot tell you what com.store exports. jdeps --list-deps reports what a module depends ON (the other direction), and jlink has no --describe option at all — it assembles runtime images.

  2. Question 2

    Compiled modules are in the directory mods. Which command launches main class com.app.Main in module com.app?

    1. A. java -cp mods -m com.app.Main

      -cp is the classpath; classes placed there join the unnamed module, so -m cannot locate a named module through it — and the -m argument is missing the module part before the slash anyway.

    2. B. java --module-path mods -m com.app/com.app.MainCorrect answer

      --module-path (short form -p) tells the launcher where to find modules, and -m (long form --module) takes an argument shaped module/mainClass — the module name first, then the fully qualified main class after the slash.

    3. C. java --module-path mods -m com.app.Main/com.app

      The slash order is reversed: the module name must come first and the main class second, so this names the main class as the module and vice versa.

    4. D. java --module mods/com.app.Main

      --module is a synonym for -m; it takes module/mainClass, not a directory path, and it does not supply the module path the launcher needs to find the modules.

    Explanation

    Launching a modular application needs the module path so the launcher can find the compiled modules, plus the module selector whose argument is shaped module-name/fully.qualified.MainClass — the module name comes first and the main class follows the slash. The classpath option cannot select a named module, because classpath code joins the unnamed module. If the module descriptor already records a main class, naming just the module is enough, but supplying an explicit main class always demands the module-before-class slash order.

  3. Question 3

    A build directory `mods` contains two artifacts: - `legacy.jar` — a plain JAR with no `module-info.class` and no `Automatic-Module-Name` in its manifest, containing the package `com.legacy`. - `com.app` — an explicit module compiled from: ```java module com.app { requires legacy; } ``` whose `com.app.Main` calls `com.legacy.Legacy.hi()`. Both `javac --module-path mods ...` and `java --module-path mods --module com.app/com.app.Main` succeed; the application prints `legacy`. The team now wants a self-contained runtime image and runs: ``` jlink --module-path mods --add-modules com.app --output image ``` What is the result?

    1. A. jlink fails because legacy was not named as a root; adding `--add-modules com.app,legacy` produces the image.

      Misdiagnoses the error as a resolution-root problem. Naming legacy explicitly does not help — jlink rejects the KIND of module (automatic), not its absence from the root set, so it just reports the same error sooner.

    2. B. The image is created; jlink copies legacy.jar into image/lib and the packaged application runs normally.

      Assumes jlink is a packager that bundles whatever is on the module path. jlink links MODULES, and only explicit or JDK modules qualify; an automatic module has no descriptor to close over, so linking fails.

    3. C. The image is created, but launching it fails because the automatic module was linked without its exports.

      Pushes the failure to run time. There is no image to launch — jlink exits non-zero and produces no output directory, because it cannot resolve the automatic module's "everything" requires/exports into a fixed image.

    4. D. jlink fails with `Error: automatic module cannot be used with jlink: legacy`.Correct answer

      A plain JAR on the module path is an automatic module; javac and java accept it, but jlink needs a closed, statically-known module graph and an automatic module has no descriptor, so resolving root com.app pulls in `requires legacy` and jlink rejects it transitively.

    Explanation

    A plain JAR dropped on the module path becomes an *automatic module*: javac and java both accept it, deriving the name `legacy` from the file name and treating it as if it exported everything and read everything. jlink is the one tool in the chain that refuses. jlink must compute a closed, statically-known module graph in order to link it into an image, and an automatic module has no descriptor to close over — its `requires` set is "everything" and its `exports` set is "all packages", neither of which can be resolved into a fixed image. So jlink rejects it outright, and it rejects it *transitively*: the command above never even names `legacy`, but resolving the root `com.app` pulls `requires legacy` in, and the link fails. Why the others are wrong: `The image is created; jlink copies legacy.jar into image/lib...` assumes jlink is a packager that bundles whatever is on the module path. It is not — it links *modules*, and only explicit or JDK modules qualify. (This is also why jlink cannot consume a plain class path at all.) `The image is created, but launching it fails...` pushes the failure to run time. There is no image to launch: jlink exits non-zero and produces no output directory. `jlink fails because legacy was not named as a root...` misdiagnoses the error as a resolution-root problem. Naming legacy explicitly does not help — it is the *kind* of module that is rejected, not its absence from the root set, so adding it as a root just reports the same automatic-module error sooner. Exam tip: automatic modules are a *migration* device, and jlink is the wall they hit. To ship a jlink image you must first give every dependency a real descriptor — write `module-info.java` for it (`jdeps --generate-module-info` will draft one). Remember the asymmetry: javac ✅, java ✅, jlink ❌. The reverse trap is a question that claims automatic modules cannot be used at all — they can, everywhere except linking.

  4. Question 4

    Which of the following is a valid directive inside a module declaration (module-info.java)?

    1. A. permits com.other;

      permits belongs to sealed type declarations (sealed classes and interfaces), not to module-info; it is not a module directive.

    2. B. includes com.util;

      includes is not a Java keyword in any context, so it cannot be a module directive.

    3. C. provides com.api.Search with com.impl.LuceneSearch;Correct answer

      This is a well-formed provides directive, naming the service type and the implementation class supplied for it; provides ... with is one of the five directives a module declaration may contain.

    4. D. friend com.other;

      friend is C++ folklore, not Java; there is no such directive in a module declaration.

    Explanation

    A module declaration admits exactly five kinds of directive: requires, exports, opens, uses, and provides ... with. A well-formed provides directive names both the service type and the implementation class supplied for it. Anything invoking a keyword borrowed from sealed types, drawn from another language, or simply invented is not a module directive at all.

  5. Question 5

    Module lib exports com.lib, whose public method Api.price() returns com.util.Money from module util. Module app declares only requires lib; and calls Money m = Api.price();. Which directive in lib's module-info.java lets app compile?

    1. A. requires util;

      lib reads util, but that readability is not passed on, so app cannot see com.util and fails with package com.util is not visible.

    2. B. requires static util;

      static makes util optional at run time for lib; it still gives app no readability of util, so app fails to compile the same way.

    3. C. requires transitive util;Correct answer

      requires transitive gives every module that reads lib implied readability of util, so app can name com.util.Money without declaring util itself.

    4. D. exports com.util;

      A module can export only its own packages; com.util belongs to util, so lib cannot export it.

    Explanation

    Readability does not pass along a dependency chain by default. When lib's exported API exposes a type from util, a consumer of lib must also read util, or it cannot name that type. requires transitive grants implied readability: any module that requires lib then reads util automatically. A plain requires keeps util private to lib, and requires static only makes util optional at run time; neither gives app anything. A module can export only its own packages, so lib cannot export com.util.

  6. Question 6

    What does the directive `exports com.a.internal to com.friend;` accomplish?

    1. A. com.friend additionally gains deep reflective access to private members of com.a.internal

      Deep reflection is granted only by opens (or an open module); a qualified export still enforces normal access rules and does not expose private members.

    2. B. The package is exported to all modules, with com.friend receiving it transitively

      The to clause narrows the export to the named modules rather than broadcasting it to all; transitivity is a property of requires, not of exports.

    3. C. com.friend's own packages are re-exported by the declaring module

      An export never touches the friend module's packages; the directive flows one way, exposing only the declaring module's own package.

    4. D. Only module com.friend can access the public types of com.a.internal at compile time and run time; other modules cannotCorrect answer

      A qualified export (exports ... to) restricts access to the listed friend modules only: com.friend can use the package's public types at compile time and run time while every other module is denied. It is the fine-grained escape hatch for sharing internals with a chosen few.

    Explanation

    A qualified export — exports ... to — narrows a package's accessibility to only the modules named after the to clause, letting them use its public types at both compile time and run time while denying every other module. It is the fine-grained escape hatch for sharing internals with a chosen few, and it flows one way: it exposes the declaring module's package without touching the friend's packages. Note that it grants ordinary access to public types only; runtime deep reflection over private members comes from opening a package, not exporting it.

  7. Question 7

    A framework in module framework, which declares requires acme.app;, must call setAccessible(true) on a private field of com.acme.domain.Order in module acme.app. The acme.app team does not want com.acme.domain to be part of its compile-time API. Which directive in acme.app's module-info.java meets both requirements?

    1. A. opens com.acme.domain;Correct answer

      opens grants deep reflective access at run time, including setAccessible on private members, while leaving the package unexported, so no module can compile against it.

    2. B. exports com.acme.domain;

      exports makes the package a compile-time API, which the team wants to avoid, and still refuses setAccessible on private members: the call throws InaccessibleObjectException.

    3. C. exports com.acme.domain to framework;

      A qualified export limits who may use the package but grants the same shallow access as exports, so setAccessible on a private field still throws InaccessibleObjectException.

    4. D. requires transitive com.acme.domain;

      requires takes a module name, not a package, and declares what acme.app depends on; it grants other modules nothing about acme.app's own packages.

    Explanation

    Module directives separate two kinds of access. exports makes a package's public types available to other modules at compile time and run time, but only shallowly: reflection may not break into private members. opens grants deep reflective access at run time, including setAccessible on private members, without exporting the package at compile time. A framework that reflects into private fields therefore needs the package opened, and a team that wants the package kept out of its compile-time API should open it rather than export it. Qualifying an export with to changes who may use the package, not how deep the access goes.

  8. Question 8

    Module com.app consumes the service interface com.api.Search via ServiceLoader, and module com.impl supplies the implementation com.impl.LuceneSearch. Which pair of module-info directives wires this correctly?

    1. A. com.app: provides com.api.Search; — com.impl: uses com.impl.LuceneSearch;

      The consumer and provider roles are reversed here; also, provides without a with clause is a syntax error, and uses must name the service interface, never the implementation class.

    2. B. com.app: requires com.impl; — com.impl: exports com.impl;

      A direct requires couples the consumer to one concrete provider, defeating the decoupling that services exist to provide; it wires modules together rather than registering or consuming a service.

    3. C. com.app: opens com.api.Search; — com.impl: exports com.impl.LuceneSearch;

      opens and exports control access to PACKAGES; neither directive registers a service nor declares its consumption, so this pair does not wire a ServiceLoader relationship.

    4. D. com.app: uses com.api.Search; — com.impl: provides com.api.Search with com.impl.LuceneSearch;Correct answer

      The consumer declares uses <service type> and the provider declares provides <service type> with <implementation class>; ServiceLoader.load(Search.class) then discovers implementations at run time without the consumer ever requiring the provider module.

    Explanation

    A ServiceLoader relationship is wired by two matching directives: the consuming module declares that it uses the service interface, and the providing module declares that it provides that interface with a named implementation class. ServiceLoader then discovers the implementation at run time, so the consumer never has to require the provider module — that decoupling is the whole point of services. The with keyword is the tell: a provides directive is only well-formed when it names both the service type and its implementation, and the consuming side must name the interface rather than the concrete class.

Practise all 18 Java Platform Module System (JPMS) questions

OCP Java SE 21 has the full set, inside timed mock exams that mirror real exam conditions — every question with a worked explanation.

Open OCP Java SE 21

Other topics in this pack