What does UnsupportedClassVersionError mean?

Every time the structure of Java byte-code changes, the version number embedded in .class files must also change.

Java 1.2 uses major version 46
Java 1.3 uses major version 47
Java 1.4 uses major version 48
Java 5 uses major version 49
Java 6 uses major version 50
Java 7 uses major version 51
Java 8 uses major version 52

Each newer version of Java fully supports classes compiled with an older Java version. A class file complied with JDK 6 is supported on Java 7 and Java 8, but it is not supported by Java 5 or earlier.

If a .class file compiled with a later JDK version is attempted to be used on an older Java version, then the java.lang.UnsupportedClassVersionError is raised by the Java runtime.

For example given this error:


java.exe -jar ords.war

Exception in thread "main" java.lang.

UnsupportedClassVersionError: oracle/dbtool
s/jarcl/Entrypoint (Unsupported major.minor version 50.0)
at java.lang.ClassLoader.defineClass0(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:539)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:123)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:251)
at java.net.URLClassLoader.access$100(URLClassLoader.java:55)
at java.net.URLClassLoader$1.run(URLClassLoader.java:194)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:187)
at java.lang.ClassLoader.loadClass(ClassLoader.java:289)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:274)
at java.lang.ClassLoader.loadClass(ClassLoader.java:235)
at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:302)

We can deduce that the Java version being used is Java 5 or earlier, since Java 6 or later will fully support .class files with a major version number of 50.

The Java version being used can be checked using the following command:


java -version

On UNIX systems, you can also use the which command to determine which Java executable is being used. On Windows you can use the where command.

In the above example it turned out that a 1.4.2 JRE that shipped with another product, was being picked up because of the way the Windows PATH environment variable was configured.

Advertisements

Hex Dumps in Eclipse Detail Formatters

If you’ve got an object that can be represented as a byte[] array, and you want to view the bytes as a hex dump in Eclipse you can use a snippet like the following in a custom Detail Formatter to do so (in this example introspecting a ByteBuffer):

new sun.misc.HexDumpEncoder().encode(this.array())

Java Servlets and URI Parameters

What’s a URI Parameter? well it’s not the values after the question mark in the URI below:

/some/path?key=value

those are HTML Form values. Actually URI Parameters are rarely used, and also poorly understood by servlet containers, here’s an example URI:

/some/path;param

To be pedantic (and this post is full of pedantry!) I should probably call them something like ‘URI Path Segment Parameters’, but for the sake of brevity I’ll continue to say URI Parameter for the rest of this document.

The HTTP 1.1 specification [1] never refers to URI parameters specifically but it does say:

   For definitive information on
   URL syntax and semantics, see "Uniform Resource Identifiers (URI):
   Generic Syntax and Semantics," RFC 2396 [42] (which replaces RFCs
   1738 [4] and RFC 1808 [11]). This specification adopts the
   definitions of "URI-reference", "absoluteURI", "relativeURI", "port",
   "host","abs_path", "rel_path", and "authority" from that
   specification.

RFC 2396 [2] has this to say about the abs_path definition:

abs_path         = "/"  path_segments
...
path_segments = segment *( "/" segment )
segment          = *pchar *( ";" param )
param             = *pchar

pchar              = unreserved | escaped |
                         ":" | "@" | "&" | "=" | "+" | "$" | ","

The path may consist of a sequence of path segments separated by a
single slash "/" character.  Within a path segment, the characters
"/", ";", "=", and "?" are reserved.  Each path segment may include a
sequence of parameters, indicated by the semicolon ";" character.
The parameters are not significant to the parsing of relative
references.

Note how parameters are permitted on each segment of the path.

As an aside RFC 2396 has been obsoleted by RFC 3986 [3], which seems to widen the definition of a URI parameter:

sub-delims  = "!" / "$" / "&" / "'" / "(" / ")"
                    / "*" / "+" / "," / ";" / "="
segment     = *pchar
pchar         = unreserved / pct-encoded / sub-delims / ":" / "@"
...
Aside from dot-segments in hierarchical paths, a path segment is
considered opaque by the generic syntax.  URI producing applications
often use the reserved characters allowed in a segment to delimit
scheme-specific or dereference-handler-specific subcomponents.  For
example, the semicolon (";") and equals ("=") reserved characters are
often used to delimit parameters and parameter values applicable to
that segment.  The comma (",") reserved character is often used for
similar purposes.  For example, one URI producer might use a segment
such as "name;v=1.1" to indicate a reference to version 1.1 of
"name", whereas another might use a segment such as "name,1.1" to
indicate the same.  Parameter types may be defined by scheme-specific
semantics, but in most cases the syntax of a parameter is specific to
the implementation of the URI's dereferencing algorithm.

So a parameter is now anything that follows a sub-delims character, not just a semi-colon. However since HTTP 1.1 depends on RFC 2396 I don’t think the above is directly relevant to this discussion, and I don’t expect containers to support this syntax.

What does the Servlet Specification [4] have to say about URI Parameters? Very little, if anything, just the following:

Path parameters that are part of a GET request (as defined by HTTP 1.1) are not
exposed by these APIs. They must be parsed from the String values returned by
the getRequestURI method or the getPathInfo method.

Now this statement raises a couple of questions:

  • What is a ‘path parameter’? HTTP 1.1 never uses this term, I’m inferring that its a URI parameter
  • Why does it state they only apply to GET requests?

So I’m unclear whether the above statement is meant to apply to URI Parameters or not. The one inference I will draw from it is that all data passed in the request URI should be retrievable from getRequestURI(), and the path portion (following the portion of the path mapped to the servlet path) of the request URI should be retrievable from getPathInfo().

The javadocs for these methods also imply this:

java.lang.String getRequestURI()

Returns the part of this request’s URL from the protocol name up to the query string in the first line of the HTTP request. The web container does not decode this String. For example:

java.lang.String getPathInfo()

Returns any extra path information associated with the URL the client sent when it made this request. The extra path information follows the servlet path but precedes the query string and will start with a “/” character.

This method returns null if there was no extra path information.

Same as the value of the CGI variable PATH_INFO.

Returns:
a String, decoded by the web container, specifying extra path information that comes after the servlet path but before the query string in the request URL; or null if the URL does not have any extra path information

Servlet containers don’t handle URI Parameters the way I expect

I wrote a little servlet (mapped to /*) to test how various servlet containers (just the ones I had to hand, there’s plenty more I didn’t test) handle URI Parameters. The servlet issues a temporary redirect to the following path and then displays the values returned from the HttpServletRequest interface:

a,b/c;d/e.f;g/h?i=j+k&l=m

This is the output I expected:

getServerInfo getPathInfo getQueryString getRequestURI getRequestURL
Container a,b/c;d/e.f;g/h i=j+k&l=m /servlet-uri-handling/a,b/c;d/e.f;g/h http://localhost:8080/servlet-uri-handling/a,b/c;d/e.f;g/h

Let’s take a look at the actual results…

getServerInfo getPathInfo getQueryString getRequestURI getRequestURL
Apache Tomcat/7.0.12 /a,b/c/e.f/h i=j+k&l=m /servlet-uri-handling/a,b/c;d/e.f;g/h http://localhost:8080/servlet-uri-handling/a,b/c;d/e.f;g/h
jetty/6.1.26 /a,b/c i=j+k&l=m /servlet-uri-handling/a,b/c;d/e.f;g/h http://localhost:8080/servlet-uri-handling/a,b/c;d/e.f;g/h
GlassFish Server Open Source Edition 3.0.1 /a,b/c i=j+k&l=m /servlet-uri-handling/a,b/c;d/e.f;g/h http://localhost:8080/servlet-uri-handling/a,b/c;d/e.f;g/h
WebLogic Server 10.3.4.0 /a,b/c i=j+k&l=m /servlet-uri-handling/a,b/c;d/e.f;g/h http://localhost:7001/servlet-uri-handling/a,b/c;d/e.f;g/h
Oracle Containers for J2EE 10g (10.1.3.5.0) /a,b/c i=j+k&l=m /servlet-uri-handling/a,b/c http://localhost:8888/servlet-uri-handling/a,b/c

Some observations:

  • As expected, none of the containers follow the wider spec of RFC 3986, treating any of the sub-delimiters as parameter markers, they only treat the semi-colon as the parameter marker.
  • All containers do recognize the presence of URI parameters
  • All containers discard any kind of URI parameter data when returning the value of getPathInfo(). Not sure why they do this, I can’t see anything in the Servlet specification telling them to do this.
  • Tomcat is the only container that understands that each segment of the path can have parameters. All the others assume the parameters can only appear on the last segment.
  • All containers bar OC4J will return the full path information (including context path, up to the query string) from getRequestURI().

Conclusion

If you need to handle URI parameters don’t use getPathInfo(), use getRequestURI() and parse out the information you need yourself, remember to URL decode if needed. If you’re tied to OC4J then you’re out of luck and there doesn’t seem to be a way to handle URI parameters.

References

Recording API Invocations for Debugging

Today I encountered a problem when using the Oracle JDBC API that only manifested itself in a certain complicated sequence of API invocations. I wanted to understand exactly what the sequence of API invocations was and also be able to extract them, so I could spit them out into a standalone test-case that I could use to debug the problem. Below is a class I whipped up to help automate most of this task.

So now I can do something like:

Invocations t = new Invocations();
PoolDataSource ods= t.trace(PoolDataSourceFactory.getPoolDataSource(),PoolDataSource.class);

ods.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");
ods.setURL("jdbc:oracle:thin://localhost:1521/orcl");
ods.setUser("scott");
ods.setPassword("tiger");
Connection conn = ods.getConnection();
CallableStatement stmt = conn.prepareCall("begin dbms_output.put_line('Hello'); end;");
stmt.execute();
stmt.close();
conn.close();
System.out.print(t);

Which produces the following output:

oracle.ucp.jdbc.PoolDataSource v1;
v1.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");
v1.setURL("jdbc:oracle:thin://localhost:1521/orcl");
v1.setUser("scott");
v1.setPassword("tiger");
java.sql.Connection v2;
v2 = v1.getConnection();
java.sql.CallableStatement v3;
v3 = v2.prepareCall("begin dbms_output.put_line('Hello'); end;");
boolean v4;
v4 = v3.execute();
v3.close();
v2.close();

Which is a pretty good facsimile of the original code. Note there are obviously limitations to what this kind of approach can achieve:

  • Invocations can only handle primitive values and values created within the API itself
  • Any non primitive values (including arrays) created outside of the API can only be declared, code to initialize these values will need to be added manually.

The major benefit of the generated code is that it does not have any dependencies other than the API being traced, thus making it easier to extract a stand-alone test case that exhibits the problem. This makes it easier to focus on the problem by eradicating superfluous code. It also very useful when you need a stand-alone test-case for a bug report for a third party API.

The Source Code

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.IdentityHashMap;

public class Invocations {

	@Override
	public String toString() {
		return b.toString();
	}
	public <T> T trace(final Object target, final Class<T> type) {
		return trace(target, type, false);
	}

	private void declare(final Object object, final Class<?> type,
			final boolean force) {
		if (!void.class.equals(type)) {
			if (force
					|| (object != null && !variables.containsKey(object) && !isPrimitive(type))) {
				b.append(typeName(type));
				b.append(" ");
				b.append(register(object));
				b.append(";\n");
			}
		}
	}
	private boolean isPrimitive(final Class<?> type) {
		boolean isPrimitive = type.isPrimitive();
		if (!isPrimitive) {
			for (final Class<?> c : PRIMITIVES) {
				if (c.equals(type)) {
					isPrimitive = true;
					break;
				}
			}
		}
		return isPrimitive;
	}
	private String name(final Object object) {
		String name = "null";
		if (object != null) {
			name = variables.get(object);
			if (name == null) {
				if (isPrimitive(object.getClass())) {
					return render(object);
				} else {
					throw new RuntimeException("Could not find name for: "
							+ object);
				}
			}
		}
		return name;
	}
	private String register(final Object object) {
		String name;
		name = variables.get(object);
		if (name == null) {
			name = "v" + (++var);
			variables.put(object, name);
		}
		return name;
	}
	private String render(final Object object) {
		final Class<?> type = object.getClass();
		if (String.class.equals(type)) {
			return "\"" + object.toString() + "\"";
		} else if (short.class.equals(type) || Short.class.equals(type)) {
			return "(short)" + object.toString();
		} else if (long.class.equals(type) || Long.class.equals(type)) {
			return object.toString() + "L";
		} else if (char.class.equals(type) || Character.class.equals(type)) {
			return "'" + object.toString() + "'";
		} else {
			return object.toString();
		}
	}
	@SuppressWarnings("unchecked")
	private <T> T trace(final Object target, final Class<T> type,
			final boolean force) {
		T result = null;
		if (target != null) {
			if (target.getClass().getInterfaces().length > 0
					&& !isPrimitive(target.getClass())) {
				result = type.cast(Proxy.newProxyInstance(Thread
						.currentThread().getContextClassLoader(), target
						.getClass().getInterfaces(), new Recorder(target)));
			} else {
				result = (T) target;
			}
		}
		declare(result, type, force);
		return result;
	}
	private String typeName(final Class<?> type) {
		if (type.isArray()) {
			return typeName(type.getComponentType()) + "[]";
		} else {
			return type.getName();
		}
	}

	private class Recorder implements InvocationHandler {

		Recorder(final Object target) {
			this.target = target;
		}

		public Object invoke(final Object proxy, final Method method,
				final Object[] args) throws Throwable {
			Object result = null;
			final Class<?> type = method.getReturnType();
			try {
				result = method.invoke(target, args);
			} catch (final InvocationTargetException e) {
				final Throwable t = e.getCause();
				for (final Class<?> ex : method.getExceptionTypes()) {
					if (ex.isAssignableFrom(t.getClass())) {
						throw t;
					}
				}
				throw e;
			}
			result = trace(result, type, true);
			record(result, proxy, method, args);
			return result;
		}

		private void record(final Object result, final Object proxy,
				final Method method, final Object[] args) {
			if (args != null) {
				final Class<?>[] types = method.getParameterTypes();
				for (int i = 0; i < args.length; ++i) {
					declare(args[i], types[i], false);
				}
			}
			if (result != null) {
				b.append(name(result));
				b.append(" = ");
			}
			b.append(name(proxy));
			b.append('.');
			b.append(method.getName());
			b.append('(');
			if (args != null) {
				for (int i = 0; i < args.length; ++i) {
					b.append(name(args[i]));
					if (i < args.length - 1) {
						b.append(',');
					}
				}
			}
			b.append(");\n");
		}

		private final Object target;

	};

	private final StringBuilder b = new StringBuilder();

	private int var = 0;

	private final IdentityHashMap<Object, String> variables = new IdentityHashMap<Object, String>();
	private static final Class<?>[] PRIMITIVES = { String.class, Integer.class,
			Boolean.class, Double.class, Long.class, Short.class, Byte.class,
			Character.class, Float.class };
}

I’ve only used this code with the specific problem I was facing, I’m sure there are issues with it, I haven’t tested it comprehensively, YMMV. For example I know it doesn’t handle arrays very well, particularly those with multiple dimensions.