45

I was trying to invoke a custom url from my java program, hence I used something like this:

URL myURL;
try {
   myURL = new URL("CustomURI:");
   URLConnection myURLConnection = myURL.openConnection();
   myURLConnection.connect();
} catch (Exception e) {
   e.printStackTrace();
}

I got the below exception:

java.net.MalformedURLException: unknown protocol: CustomURI at java.net.URL.(Unknown Source) at java.net.URL.(Unknown Source) at java.net.URL.(Unknown Source) at com.demo.TestDemo.main(TestDemo.java:14)

If I trigger the URI from a browser then it works as expected but if I try to invoke it from the Java Program then I am getting the above exception.

EDIT:

Below are the steps I tried (I am missing something for sure, please let me know on that):

Step 1: Adding the Custom URI in java.protocol.handler.pkgs

Step 2: Triggering the Custom URI from URL

Code:

public class CustomURI {

public static void main(String[] args) {

    try {
        add("CustomURI:");
        URL uri = new URL("CustomURI:");
        URLConnection uc = uri.openConnection();            
        uc.connect();
    } catch (Exception e) {
        e.printStackTrace();
    }

}

public static void add( String handlerPackage ){

    final String key = "java.protocol.handler.pkgs";

    String newValue = handlerPackage;
    if ( System.getProperty( key ) != null )
    {
        final String previousValue = System.getProperty( key );
        newValue += "|" + previousValue;
    }
    System.setProperty( key, newValue );
    System.out.println(System.getProperty("java.protocol.handler.pkgs"));

}

}

When I run this code, I am getting the CustomURI: printed in my console (from the add method) but then I am getting this exception when the URL is initialized with CustomURI: as a constructor:

Exception in thread "main" java.lang.StackOverflowError
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Unknown Source)
at java.net.URL.getURLStreamHandler(Unknown Source)
at java.net.URL.<init>(Unknown Source)
at java.net.URL.<init>(Unknown Source)
at sun.misc.URLClassPath$FileLoader.getResource(Unknown Source)
at sun.misc.URLClassPath.getResource(Unknown Source)
at java.net.URLClassLoader$1.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at java.net.URL.getURLStreamHandler(Unknown Source)
at java.net.URL.<init>(Unknown Source)
at java.net.URL.<init>(Unknown Source)

Please advice how to make this work.

Olimpiu POP
  • 5,001
  • 4
  • 34
  • 49
user182944
  • 7,897
  • 33
  • 108
  • 174
  • 1
    What is _as expected_? – Sotirios Delimanolis Oct 14 '14 at 14:39
  • `As Expected` means that custom URL triggers an application. Basically this Custom URL is a registry entry which I created for triggering desktop applications. – user182944 Oct 14 '14 at 14:40
  • 3
    I'm pretty sure you're going to have to write your own `URLStreamHandlerFactory` for handling that protocol. – Sotirios Delimanolis Oct 14 '14 at 14:48
  • 2
    I think similar question was answered http://stackoverflow.com/questions/2406518/why-does-javas-url-class-not-recognize-certain-protocols – Lemonov Oct 14 '14 at 14:49
  • Any sample code example for working with `URLStreamHandlerFactory` is appreciated. I am not getting much examples on this. – user182944 Oct 14 '14 at 15:27
  • Why don't you use the `URI` class? – fge Oct 17 '14 at 07:28
  • When modifying the property, you are putting the old value at the end of the new value. This implies that your handler is attempted to be queried first, but for loading your class, the URL of your class has to be opened, so the JRE tries to find a handler and will attempt your handler first, so your class must be loaded, but for that the URL of your class has to be opened, so the JRE tries to find a handler and will attempt your handler first, so your class must be loaded, but for that the URL of your class has to be opened, so the JRE tries to find a handler and will attempt your handler fir… – Holger Oct 17 '14 at 11:18

3 Answers3

89
  1. Create a custom URLConnection implementation which performs the job in connect() method.

    public class CustomURLConnection extends URLConnection {
    
        protected CustomURLConnection(URL url) {
            super(url);
        }
    
        @Override
        public void connect() throws IOException {
            // Do your job here. As of now it merely prints "Connected!".
            System.out.println("Connected!");
        }
    
    }
    

    Don't forget to override and implement other methods like getInputStream() accordingly. More detail on that cannot be given as this information is missing in the question.


  2. Create a custom URLStreamHandler implementation which returns it in openConnection().

    public class CustomURLStreamHandler extends URLStreamHandler {
    
        @Override
        protected URLConnection openConnection(URL url) throws IOException {
            return new CustomURLConnection(url);
        }
    
    }
    

    Don't forget to override and implement other methods if necessary.


  3. Create a custom URLStreamHandlerFactory which creates and returns it based on the protocol.

    public class CustomURLStreamHandlerFactory implements URLStreamHandlerFactory {
    
        @Override
        public URLStreamHandler createURLStreamHandler(String protocol) {
            if ("customuri".equals(protocol)) {
                return new CustomURLStreamHandler();
            }
    
            return null;
        }
    
    }
    

    Note that protocols are always lowercase.


  4. Finally register it during application's startup via URL#setURLStreamHandlerFactory()

    URL.setURLStreamHandlerFactory(new CustomURLStreamHandlerFactory());
    

    Note that the Javadoc explicitly says that you can set it at most once. So if you intend to support multiple custom protocols in the same application, you'd need to generify the custom URLStreamHandlerFactory implementation to cover them all inside the createURLStreamHandler() method.


    Alternatively, if you dislike the Law of Demeter, throw it all together in anonymous classes for code minification:

    URL.setURLStreamHandlerFactory(new URLStreamHandlerFactory() {
        public URLStreamHandler createURLStreamHandler(String protocol) {
            return "customuri".equals(protocol) ? new URLStreamHandler() {
                protected URLConnection openConnection(URL url) throws IOException {
                    return new URLConnection(url) {
                        public void connect() throws IOException {
                            System.out.println("Connected!");
                        }
                    };
                }
            } : null;
        }
    });
    

    If you're on Java 8 already, replace the URLStreamHandlerFactory functional interface by a lambda for further minification:

    URL.setURLStreamHandlerFactory(protocol -> "customuri".equals(protocol) ? new URLStreamHandler() {
        protected URLConnection openConnection(URL url) throws IOException {
            return new URLConnection(url) {
                public void connect() throws IOException {
                    System.out.println("Connected!");
                }
            };
        }
    } : null);
    

Now you can use it as follows:

URLConnection connection = new URL("CustomURI:blabla").openConnection();
connection.connect();
// ...

Or with lowercased protocol as per the spec:

URLConnection connection = new URL("customuri:blabla").openConnection();
connection.connect();
// ...
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • 1
    I tried this (details are here http://stackoverflow.com/questions/37528167/how-to-set-source-ip-to-http-request/37532231#37532231) Would it be possible for you to provide implementation of `getInputStream()` – theHeman May 31 '16 at 07:04
  • 2
    Do we need to call `URL.setURLStreamHandlerFactory()` if we put all necessary classes into the "default package"? (which one is it btw?) – Matthieu Jun 03 '16 at 17:37
  • Can you provide `getInputStream()` / `getOutputStream()` implementation please? Or at least give a hint how to do that? That would be pleasure! :) – shurrok Jan 14 '18 at 22:41
  • 1
    After java 9+, this is very easy do it by https://stackoverflow.com/a/56088592/855343 example – rombow Jan 22 '20 at 14:08
  • Note, if you are writing a library where it might be uncouth to take over the one-and-only instance of URLStreamHandlerFactory, you might want to check my answer, which uses a different mechanism to add support for a given protocol: without taking over the only instance of URLStreamHandlerFactory. – Ajax Nov 02 '21 at 22:33
13

If you don't want to take over the one-and-only URLStreamHandlerFactory, you can actually use a hideous, but effective naming convention to get in on the default implementation.

You must name your URLStreamHandler class Handler, and the protocol it maps to is the last segment of that class' package.

So, com.foo.myproto.Handler->myproto:urls, provided you add your package com.foo to the list of "url stream source packages" for lookup on unknown protocol. You do this via system property "java.protocol.handler.pkgs" (which is a | delimited list of package names to search).

Here is an abstract class that performs what you need: (don't mind the missing StringTo<Out1<String>> or StringURLConnection, these do what their names suggest and you can use whatever abstractions you prefer)

public abstract class AbstractURLStreamHandler extends URLStreamHandler {

    protected abstract StringTo<Out1<String>> dynamicFiles();

    protected static void addMyPackage(Class<? extends URLStreamHandler> handlerClass) {
        // Ensure that we are registered as a url protocol handler for JavaFxCss:/path css files.
        String was = System.getProperty("java.protocol.handler.pkgs", "");
        String pkg = handlerClass.getPackage().getName();
        int ind = pkg.lastIndexOf('.');
        assert ind != -1 : "You can't add url handlers in the base package";
        assert "Handler".equals(handlerClass.getSimpleName()) : "A URLStreamHandler must be in a class named Handler; not " + handlerClass.getSimpleName();

        System.setProperty("java.protocol.handler.pkgs", handlerClass.getPackage().getName().substring(0, ind) +
            (was.isEmpty() ? "" : "|" + was ));
    }


    @Override
    protected URLConnection openConnection(URL u) throws IOException {
        final String path = u.getPath();
        final Out1<String> file = dynamicFiles().get(path);
        return new StringURLConnection(u, file);
    }
}

Then, here is the actual class implementing the abstract handler (for dynamic: urls:

package xapi.dev.api.dynamic;

// imports elided for brevity

public class Handler extends AbstractURLStreamHandler {

    private static final StringTo<Out1<String>> dynamicFiles = X_Collect.newStringMap(Out1.class,
        CollectionOptions.asConcurrent(true)
            .mutable(true)
            .insertionOrdered(false)
            .build());

    static {
        addMyPackage(Handler.class);
    }

    @Override
    protected StringTo<Out1<String>> dynamicFiles() {
        return dynamicFiles;
    }

    public static String registerDynamicUrl(String path, Out1<String> contents) {
        dynamicFiles.put(path, contents);
        return path;
    }

    public static void clearDynamicUrl(String path) {
        dynamicFiles.remove(path);
    }

}
Ajax
  • 2,465
  • 23
  • 20
2

You made a recursion/endless-loop.

The Classloader searching for the class in different ways.

The stacktrace (URLClassPath) is like this:

  1. Load a resource.
  2. Did i load every protocol? No!
  3. Load all protocoll-Handlers, i cant find the File «your java.protocol.handler.pkgs-package».CustomURI.Handler.
  4. The class is a resource! Did i load every protocol? No!
  5. Load all protocoll-Handlers, i cant find the File «your java.protocol.handler.pkgs-package».CustomURI.Handler.
  6. The class is a resource! Did i load every protocol? No!
  7. Load all protocoll-Handlers, i cant find the File «your java.protocol.handler.pkgs-package».CustomURI.Handler.
  8. The class is a resource! Did i load every protocol? No!
  9. Load all protocoll-Handlers, i cant find the File «your java.protocol.handler.pkgs-package».CustomURI.Handler.
  10. The class is a resource! Did i load every protocol? No!
  11. Load all protocoll-Handlers, i cant find the File «your java.protocol.handler.pkgs-package».CustomURI.Handler.

    ...... StackOverflowException!!!

Grim
  • 1,938
  • 10
  • 56
  • 123