JDK-8358341 : Support the Manifest Class-Path attribute in jar files on the --processor-path
  • Type: Enhancement
  • Component: tools
  • Sub-Component: javac
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • Submitted: 2025-06-02
  • Updated: 2025-10-14
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
tbdUnresolved
Related Reports
Relates :  
Relates :  
Description
`javac` initially didn't support the `Class-Path` attribute in Manifest files [1,2] for jar-files on the `--class-path` at all. This support was only added in JDK 5.0 by JDK-4212732 and the documentation of the feature is still pending and currently targeted for JDK 26 (see JDK-8278856).

`javac` currently doesn't support (i.e. ignores) `Class-Path` attribute in Manifest files [1,2] for jar-files on the `--processor-path`. Java annotation processors can be placed either on the `--class-path` or on the `--processor-path` [3] and the inconsistent handling of the `Class-Path` attribute, depending on whether a jar-file is placed on the `--class-path` or the `--processor-path`, makes it difficult to migrate annotations processors from the  `--class-path` to the `--processor-path`.

Annotation processors are executed in the "runtime environment" as opposed to the "compilation environment" [4]. This distinction is especially important if annotation processors are placed on the `--processor-path`, because in such cases, their dependencies can't be put on the normal `--class-path` but instead have to be placed either on the `--processor-path` as well or they will have to made available directly to the host JVM with the help of `-J--class-path` options.

It would be helpful if `javac` could be extended such that it honors the `Class-Path` attribute in Manifest files for jar-files on the `--processor-path` such that it adds the attribute's value to the `--processor-path`.


[1] https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#classpath
[2] https://docs.oracle.com/javase/tutorial/deployment/jar/downman.html
[3] https://docs.oracle.com/en/java/javase/24/docs/specs/man/javac.html#annotation-processing
[4] https://docs.oracle.com/en/java/javase/24/docs/specs/man/javac.html#compilation-environment-and-runtime-environment.
Comments
Hi [~jlahoda], Thanks for looking into this issue and sorry for the late response (I was attending Devoxx last week). I've attached an example test which illustrates the issue I wanted to address. In the example , the annotation processor is in a jar file which has a dependency which is declared in the Class-Path attribute of its manifest. It works fine if the annotation processor is run from the class path but it fails if we put the annotation processor on the processor path. In the latter case, it also doesn't help to manually add the dependency to the class path (as the test demonstrate). This is clear because of: Annotation processors are executed in the "runtime environment" as opposed to the "compilation environment" [4]. This distinction is especially important if annotation processors are placed on the `--processor-path`, because in such cases, their dependencies can't be put on the normal `--class-path` but instead have to be placed either on the `--processor-path` as well or they will have to made available directly to the host JVM with the help of `-J--class-path` options. And that's actually exactly what this issue is about - a request to treat the Class-Path attribute of a manifest of a jar file on the processor path in such a way that it extends visible classes of the "runtime environment" as if the classes mentioned in the Class-Path attribute were manually added to the processor path. This is an issue we ran into when we were trying to follow the general recommendation to move annotation processors from the class path to the processor path. PS: I've intentionally changed the `Task.Mode` to `EXEC` in my version of the test in order to make it possible to observe the exact javac command line in the `.jtr` file by setting a corresponding logging configuration with e.g. `-javaoption:-Djava.util.logging.config.file=/tmp/logging.properties`. See `ProcessBuilder.start()` for more details.
14-10-2025

I wonder if there is a reproducible test case showing the problem. The question I am asking is that I wrote a test (see below), and it seems the `Class-Path` attribute is being used for `-processorpath` for me. It is true that javac's JavacFileManager itself won't expand the attribute, but it uses URLClassLoader to load the classes, and URLClassLoader does follow the Class-Path attribute. So, I wonder what exactly are the circumstances when the attribute is not followed. The test I wrote so far: ``` /* * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /** * @test * @bug 8358341 * @summary Verify that the Class-Path manifest attribute is reflected for processorpath * @library /tools/lib * @modules jdk.compiler/com.sun.tools.javac.api * jdk.compiler/com.sun.tools.javac.code * jdk.compiler/com.sun.tools.javac.main * @build toolbox.ToolBox FollowClassPathAttributeInProcessorPath * @run junit FollowClassPathAttributeInProcessorPath */ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; import java.util.Objects; import javax.annotation.processing.Processor; import org.junit.jupiter.api.Test; import toolbox.JavacTask; import toolbox.JarTask; import toolbox.Task; import toolbox.ToolBox; public class FollowClassPathAttributeInProcessorPath { ToolBox tb = new ToolBox(); @Test public void testDirectoryStreamClosed() throws IOException { Path base = Paths.get("."); Path processor = base.resolve("processor"); Path processorSrc = processor.resolve("src"); Path processorClasses = processor.resolve("classes"); tb.writeJavaFiles(processorSrc, """ package proc; import java.util.Set; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.lang.model.SourceVersion; import javax.lang.model.element.TypeElement; @SupportedAnnotationTypes("*") public class ProcessorImpl extends AbstractProcessor { public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { if (roundEnv.processingOver()) { processingEnv.getMessager().printNote("Called!"); } return false; } public SourceVersion getSupportedSourceVersion() { return SourceVersion.latest(); } } """); Files.createDirectories(processorClasses); new JavacTask(tb) .outdir(processorClasses) .files(tb.findJavaFiles(processorSrc)) .run() .writeAll(); tb.writeFile(processorClasses.resolve("META-INF/services").resolve(Processor.class.getName()), """ proc.ProcessorImpl """); List<String> expected = List.of( "Note: Called!" ); List<String> out; out = new JavacTask(tb) .options("-processorpath", processorClasses.toString()) .classes("java.lang.Object") .run() .writeAll() .getOutputLines(Task.OutputKind.DIRECT); if (!Objects.equals(expected, out)) { throw new AssertionError("Unexpected result: " + out + ", expected: " + expected); } Path processorJar = base.resolve("processor.jar"); new JarTask(tb, processorJar) .baseDir(processorClasses) .files("META-INF/services/" + Processor.class.getName(), "proc/ProcessorImpl.class") .run(); out = new JavacTask(tb) .options("-processorpath", processorJar.toString()) .classes("java.lang.Object") .run() .writeAll() .getOutputLines(Task.OutputKind.DIRECT); if (!Objects.equals(expected, out)) { throw new AssertionError("Unexpected result: " + out + ", expected: " + expected); } Path indirectJar = base.resolve("indirect.jar"); new JarTask(tb, indirectJar) .manifest(""" Class-Path: processor.jar """) .run(); out = new JavacTask(tb) .options("-classpath", indirectJar.toString(), "-proc:only") .classes("java.lang.Object") .run() .writeAll() .getOutputLines(Task.OutputKind.DIRECT); if (!Objects.equals(expected, out)) { throw new AssertionError("Unexpected result: " + out + ", expected: " + expected); } out = new JavacTask(tb) .options("-processorpath", indirectJar.toString()) .classes("java.lang.Object") .run() .writeAll() .getOutputLines(Task.OutputKind.DIRECT); if (!Objects.equals(expected, out)) { throw new AssertionError("Unexpected result: " + out + ", expected: " + expected); } out = new JavacTask(tb) .options("--processor-path", indirectJar.toString()) .classes("java.lang.Object") .run() .writeAll() .getOutputLines(Task.OutputKind.DIRECT); if (!Objects.equals(expected, out)) { throw new AssertionError("Unexpected result: " + out + ", expected: " + expected); } Path indirect2Jar = base.resolve("indirect2.jar"); new JarTask(tb, indirect2Jar) .manifest(""" Class-Path: indirect.jar """) .run(); out = new JavacTask(tb) .options("-classpath", indirect2Jar.toString(), "-proc:only") .classes("java.lang.Object") .run() .writeAll() .getOutputLines(Task.OutputKind.DIRECT); if (!Objects.equals(expected, out)) { throw new AssertionError("Unexpected result: " + out + ", expected: " + expected); } out = new JavacTask(tb) .options("-processorpath", indirect2Jar.toString()) .classes("java.lang.Object") .run() .writeAll() .getOutputLines(Task.OutputKind.DIRECT); if (!Objects.equals(expected, out)) { throw new AssertionError("Unexpected result: " + out + ", expected: " + expected); } out = new JavacTask(tb) .options("--processor-path", indirect2Jar.toString()) .classes("java.lang.Object") .run() .writeAll() .getOutputLines(Task.OutputKind.DIRECT); if (!Objects.equals(expected, out)) { throw new AssertionError("Unexpected result: " + out + ", expected: " + expected); } } } ```
07-10-2025