ExerciseWarm-up
Warm-up
Produce four linkage errors
10 minjunior0–15 yrs
One concept, guided. Near-impossible to fail.
What this teaches
- A linkage error is thrown by the JVM, not by any library
- The message names a full descriptor, which identifies the version you compiled against
- Linking is lazy and per call site, which is why tests pass
- A compile-time constant is inlined, so the same defect can fail silently
Starter
Starter.java
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;
/**
* Warm-up: break a classpath on purpose, four different ways.
*
* Everything here is the JDK. javax.tools gives you the compiler at runtime,
* and a URLClassLoader with a null parent gives you an isolated classpath —
* which together are enough to reproduce a dependency conflict without
* downloading a single jar.
*/
public class Starter {
static final JavaCompiler JAVAC = ToolProvider.getSystemJavaCompiler();
/** Compiles one source file into `out`, optionally against `classpath`. */
static void compile(Path root, String fileName, String source, Path out, Path classpath)
throws Exception {
Files.createDirectories(out);
Path file = root.resolve(fileName);
Files.writeString(file, source);
List<String> options = new ArrayList<>(List.of("-d", out.toString(), "-nowarn"));
if (classpath != null) { options.add("-cp"); options.add(classpath.toString()); }
var fm = JAVAC.getStandardFileManager(null, null, null);
if (!JAVAC.getTask(null, fm, null, options, null,
fm.getJavaFileObjects(file.toFile())).call())
throw new IllegalStateException("could not compile " + fileName);
}
public static void main(String[] args) throws Exception {
Path root = Files.createTempDirectory("warmup");
// TODO 1: compile two versions of a class called Lib into two separate
// output directories. Version one has `go(String)`. Version two has
// `go(String, int)` and nothing else.
// TODO 2: compile an App against version one, calling Lib.go("x").
// Confirm it compiles cleanly — that is the point.
// TODO 3: load App with a URLClassLoader whose URLs are [app, v2] and
// whose PARENT IS NULL, then invoke run(). Print the error.
//
// new URLClassLoader(new URL[]{ appDir.toUri().toURL(), v2.toUri().toURL() }, null)
//
// The null parent matters: without it, the loader delegates upward and
// may find classes from your own classpath instead.
// TODO 4: read the error message carefully. It names a full signature.
// Say what that tells you that a bare method name would not.
// TODO 5: repeat for a removed non-constant field (public static int
// MAX = 100). Which error do you get?
// TODO 6: repeat for an interface that gained a method your class does
// not implement. You will need three types: an interface, a caller
// that invokes the new method, and your App implementing the old
// interface. A .java file may hold several top-level types as long as
// only one is public.
// TODO 7: repeat for a class that was removed entirely.
// TODO 8: now the important one. Give App two static methods — one
// calling a method that exists in both versions, one calling the
// method that vanished. Load the class WITHOUT calling anything.
// Then call the safe method. Then call the broken one. Explain, in one
// sentence, why a passing test suite is not evidence of a clean
// classpath.
// TODO 9: make Lib expose `public static final int TIMEOUT = 30` in
// version one and 60 in version two. Compile App against version one,
// run it against version two, and print the value. Explain the result
// and say why it is more dangerous than a NoSuchFieldError.
}
}Run it locally:
cd exercises/java/dependencies/transitive-conflicts/01-warmup
javac Starter.java -d /tmp/out && java -cp /tmp/out StarterDone when
- You produced NoSuchMethodError, NoSuchFieldError, AbstractMethodError and NoClassDefFoundError for real
- You showed a class loading cleanly while one of its call sites is unlinkable
- You showed a static final constant keeping its old value after an upgrade
- You can say what the descriptor in the error message tells you
← Back to Two libraries need different versions of the same dependency. What happens?