Registering and using a custom java.net.URL protocol
Create a custom
URLConnection
implementation which performs the job inconnect()
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.Create a custom
URLStreamHandler
implementation which returns it inopenConnection()
.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.
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.
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 thecreateURLStreamHandler()
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();
// ...
Java - Registering custom URL protocol handlers
I have found the issue. The original classpath handler class that I used had a non-default constructor. Of course, because it had only a non-default constructor, the handler couldn't be instantiated. I apologize to everyone who have tried to debug this issue, I failed to see this connection.
Register custom URLStreamHandler in Spring web application (Tomcat)
You could either:
1. Use a decorator
One way to set your custom URLStreamHandlerFactory
, could be to use a decorator of type URLStreamHandlerFactory
in order to wrap the URLStreamHandlerFactory
that may have already been defined (by tomcat in this case). The tricky part is the fact that you need to use reflection (which is quite hacky) to get and reset the current factory
potentially defined.
Here is the pseudo-code of your decorator:
public class S3URLStreamHandlerFactory implements URLStreamHandlerFactory {
// The wrapped URLStreamHandlerFactory's instance
private final Optional<URLStreamHandlerFactory> delegate;
/**
* Used in case there is no existing URLStreamHandlerFactory defined
*/
public S3URLStreamHandlerFactory() {
this(null);
}
/**
* Used in case there is an existing URLStreamHandlerFactory defined
*/
public S3URLStreamHandlerFactory(final URLStreamHandlerFactory delegate) {
this.delegate = Optional.ofNullable(delegate);
}
@Override
public URLStreamHandler createURLStreamHandler(final String protocol) {
if ("s3".equals(protocol)) {
return // my S3 URLStreamHandler;
}
// It is not the s3 protocol so we delegate it to the wrapped
// URLStreamHandlerFactory
return delegate.map(factory -> factory.createURLStreamHandler(protocol))
.orElse(null);
}
}
Here is the code to define it:
// Retrieve the field "factory" of the class URL that store the
// URLStreamHandlerFactory used
Field factoryField = URL.class.getDeclaredField("factory");
// It is a package protected field so we need to make it accessible
factoryField.setAccessible(true);
// Get the current value
URLStreamHandlerFactory urlStreamHandlerFactory
= (URLStreamHandlerFactory) factoryField.get(null);
if (urlStreamHandlerFactory == null) {
// No factory has been defined so far so we set the custom one
URL.setURLStreamHandlerFactory(new S3URLStreamHandlerFactory());
} else {
// Retrieve the field "streamHandlerLock" of the class URL that
// is the lock used to synchronize access to the protocol handlers
Field lockField = URL.class.getDeclaredField("streamHandlerLock");
// It is a private field so we need to make it accessible
lockField.setAccessible(true);
// Use the same lock to reset the factory
synchronized (lockField.get(null)) {
// Reset the value to prevent Error due to a factory already defined
factoryField.set(null, null);
// Set our custom factory and wrap the current one into it
URL.setURLStreamHandlerFactory(
new S3URLStreamHandlerFactory(urlStreamHandlerFactory)
);
}
}
NB: Starting for Java 9, you will need to add --add-opens java.base/java.net=myModuleName
to your launch command to allow deep reflection on the package java.net
that includes the class URL
from your module myModuleName
otherwise calling setAccessible(true)
will raise a RuntimeException
.
2. Deploy it as an extension
Another way that should avoid this ClassLoder
issue could be to move your custom URLStreamHandler
into a dedicated jar and deploy it in ${JAVA-HOME}/jre/lib/ext
as an installed extension (with all its dependencies) such that it will be available in the Extension ClassLoader
, this way your class will be defined in a ClassLoader
high enough in the hierarchy to be seen.
Can I have a custom protocol on java type URL?
@JBNizet answer is perfect. Just use
val url = URI("http://my-page/content?page=0")
Commons VFS and Java.net.URL - Adding support for “ram://” protocol
VFS supports creating a stream handler factory which knows about all the registered schemes.
// you might want to configure a manager with less schemes
FileSystemManager fsm = VFS.getManager();
URLStreamHandlerFactory factory = fsm.getURLStreamHandlerFactory();
URL.setURLStreamHandlerFactory(factory); // VM global
URL url = new URL("ram://test.txt");
Why does Java's URL class not recognize certain protocols?
Issue
Java throws a MalformedURLException
because it couldn't find a URLStreamHandler
for that protocol. Check the javadocs of the constructors for the details.
Summary
Since the URL
class has an openConnection
method, the URL class checks to make sure that Java knows how to open a connection of the correct protocol. Without a URLStreamHandler
for that protocol, Java refuses to create a URL
to save you from failure when you try to call openConnection
.
Solution
You should probably be using the URI
class if you don't plan on opening a connection of those protocols in Java.
Related Topics
Sorting a List with Stream.Sorted() in Java
Selenium Webdriver + Java - Eclipse: Java.Lang.Noclassdeffounderror
How to Get Backspace \B to Work in Eclipse's Console
Mockito: Invaliduseofmatchersexception
What Is the Most Efficient Java Collections Library
How to Avoid a Lot of If Else Conditions
Java.Lang.Outofmemoryerror: Java Heap Space in Maven
Getting Database Connection in Pure JPA Setup
Java Lib or App to Convert CSV to Xml File
Escaping Special Characters in Java Regular Expressions
Validation of a List of Objects in Spring
Directly Convert CSV File to JSON File Using the Jackson Library
Hexadecimal to Integer in Java
Javamail API to Imail -- Java.Net.Socketexception: Permission Denied: Connect
In Which Case Do You Use the JPA @Jointable Annotation
Mapping a Specific Servlet to Be the Default Servlet in Tomcat