The custom URL handler used by my application can be used normally when running in idea, But when I package it into an executable jar, it won't work properly.

example

I have a simple example : ExampleApp, There are two ways to start. Then open http://127.0.0.1:8080/echo/lei, case two will throw java.net.MalformedURLException.

one

SpringBoot Executable jars cannot load custom URL handler

two

call java -jar .\ExampleApp-0.0.1-SNAPSHOT.jar -Djava.protocol.handler.pkgs=com.example.exampleapp in terminal

java.net.MalformedURLException: unknown protocol: echo
        at java.base/java.net.URL.<init>(URL.java:681) ~[na:na]
        at java.base/java.net.URL.<init>(URL.java:569) ~[na:na]
        at java.base/java.net.URL.<init>(URL.java:516) ~[na:na]
        at com.example.exampleapp.ExampleAppApplication.echo(ExampleAppApplication.java:22) ~[classes!/:0.0.1-SNAPSHOT]
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
        at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
        at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205) ~[spring-web-5.3.22.jar!/:5.3.22]

Is there any plan to make it work normally?

Comment From: wilkinsona

Unfortunately, URL uses its own ClassLoader to load URLStreamHandlerFactory implementations. That means that an implementation needs to be packaged in the root of the jar rather than within BOOT-INF/classes or a jar in BOOT-INF/lib. Please see https://github.com/spring-projects/spring-boot/issues/6626 for some suggestions on how to do that.

Comment From: aoyvx

@wilkinsona Thank you for your advice. But would it be a better solution to change the constants here into configuration items. https://github.com/spring-projects/spring-boot/blob/35c49afd97dd353020b96df9c537bb44d819ef7e/spring-boot-project/spring-boot-tools/spring-boot-loader-tools/src/main/java/org/springframework/boot/loader/tools/Packager.java#L439-L446

Otherwise, I prefer to use this scheme. Of course, it looks unstable.

@RestController
@SpringBootApplication
public class ExampleAppApplication implements ApplicationRunner {

    public static void main(String[] args) {
        SpringApplication.run(ExampleAppApplication.class, args);
    }

    @GetMapping("/echo/{msg}")
    public String echo(@PathVariable("msg")String msg) throws IOException {
        return new URL("echo:"+msg).getContent().toString();
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        TomcatURLStreamHandlerFactory.getInstance().addUserFactory(protocol -> {
            if(Objects.equals("echo", protocol)){
                try {
                    return (URLStreamHandler) ExampleAppApplication.class.getClassLoader()
                            .loadClass("com.example.exampleapp.echo.Handler").getDeclaredConstructor().newInstance();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return null;
        });
    }
}

Comment From: wilkinsona

But would it be a better solution to change the constants here into configuration items.

That's certainly one option, but we haven't yet decided how to solve #6626. Please follow up on #6626 if you'd like to discuss things further on how to package content in the root of the jar.

Comment From: aoyvx

@wilkinsona Sorry, I was a little careless when browsing this issue. Thank you again.