[quote]The only problem is: How?
[/quote]
You need to get the best of both worlds.
We had a base ‘CSGraphics’ class that when initialised creates either a ‘CSGraphics11’ or a ‘CSGraphics14’. the 11 used software rendering, the 14 used buffered images and simply forwarded the draw calls onto the normal Graphics object. The Sprite was wrapped up in the same way as a ‘CSSprite’, as was BufferStrategy and the Audio - although the audio is a bit buggy at the moment - 1.1 audio sucks bad! >:(
Here is the code to detect the current VM version:
// parse version, if less than 1.4 then use 1.1 compatibility
byte version[] = javaVersion.getBytes();
try
{
if (version[0] >= '1')
{
if (version[1]=='.')
{
if (version[2]>='4')
javaVersion1 = false;
}
}
}
catch (java.lang.ArrayIndexOutOfBoundsException e)
{
}
In the Java spec, it says that Java can only reference classes when the code is executed - so JIT compilers shouldn’t (& don’t) complain about 1.4 code being in your app unless it is executed.
Here is the code from our old CSGraphics class:
public abstract class CSGraphics
{
static CSGraphics alloc(int w, int h)
{
if (CSInfo.javaVersion1)
return new CSGraphics11(w, h);
else
return new CSGraphics14(w, h);
}
abstract boolean drawImage(CSSprite s, int x, int y, ImageObserver observer);
// etc...
By abstracting all the business bits into two seperate classes and using a factory class to create the right one at runtime, ensures that your code will work on 1.1 and 1.4 and you don’t take any speed penalty.
Hope this helps 
PS: We have since removed this and just use the software rendering now as we do more interesting things. Take a look at Traffic Jammer JX (Blatant plug: http://www.crystalsquid.com/games/traffic_jx.php)- it is rendered with a static z-buffer and stored background frame. The only thing rendered each frame are the cars and the particles, everything else is a single arraycopy from an image generated at the start of each level. You can’t do that with a BufferedImage ;D
[edit] And you do need the -target 1.1 too 