54

I have a method as of below which has been running properly for a long time:

private String loadFromFile(){

    RandomAccessFile inFile = null;
    FileChannel inChannel = null;
    StringBuilder sb = new StringBuilder();
    try {

        inFile = new RandomAccessFile(this.latestImageFile, "r");
        inChannel = inFile.getChannel();

        ByteBuffer bb = ByteBuffer.allocate(2046);
        while( inChannel.read(bb) != -1){
            bb.flip();

            while(bb.hasRemaining()){
                char c = (char) bb.get();   // read character at current position and set the pointer to current position + 1
                sb.append(c);
            }

            bb.clear();
        }

    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (inChannel != null) try {inChannel.close(); } catch (IOException e){}
        if (inFile != null ) try { inFile.close(); } catch (IOException e) {}
    }

    return sb.toString();
}

However, today after I have compiled and run the program on a server, below exception was logged when starting the program. It shows a flip() method is not found:

Exception in thread "main" java.lang.NoSuchMethodError: java.nio.ByteBuffer.flip()Ljava/nio/ByteBuffer;
        at com.rt.stream.s.exch.OBWorker.loadFromFile(OBWorker.java:271)
        at com.rt.stream.s.exch.OBWorker.initAndLoadOBs(OBWorker.java:184)
        at com.rt.stream.s.exch.OBWorker.<init>(OBWorker.java:145)
        at com.rt.stream.s.exch.OBWorkerMgr.initFromProperties(OBWorkerMgr.java:217)
        at com.rt.stream.s.exch.OBWorkerMgr.init(OBWorkerMgr.java:132)
        at com.rt.stream.s.exch.OBWorkerMgr.main(OBWorkerMgr.java:511)

Anybody has any idea on it please?

The program running environment specification is like this:

Server:

  1. openjdk version "1.8.0_242"

Development:

  1. IDE: Version: 2019-09 R (4.13.0)

  2. JDK: jdk-11.0.1

  3. maven: apache-maven-3.3.3 (with below configuration applied)

   <source>1.8</source>   
   <target>1.8</target>
garykwwong
  • 2,327
  • 2
  • 16
  • 20
  • 1
    Why can't you use the same jdk of your server when developing? – Lino Apr 17 '20 at 08:50
  • 3
    yea, you're right, that would be the ideal case and which should be done for a production environment. But for some situation of non-production env., e.g. we have different pet projects developed from time to time in the same IDE workspace, or even running on a shared environment with others project running on it. It could be encountered occasionally. – garykwwong Apr 17 '20 at 09:02
  • Does this answer your question? [java.nio.Buffer not loading clear() method on runtime](https://stackoverflow.com/questions/48693695/java-nio-buffer-not-loading-clear-method-on-runtime) – Janez Kuhar Aug 25 '21 at 07:40
  • I was getting the same error while using Apache NetBeans 12.6 along with jdk 1.8.0_202. I solved it by downgrading my Apache NetBeans to 12.3 – Fawad Khaliq Dec 22 '21 at 09:00

1 Answers1

71

After searching for a while and verified through switching the installed JDK between 8 and 11, I have found that there are some changes (new overridden methods) applied to several methods (e.g. flip(), clear() ) in ByteBuffer class.

In Java 8, while calling flip() method of ByteBuffer class, since it has no implementation for this method, so it is actually calling the method from extended class, Buffer; which is returning Buffer object as below:

In Buffer class:

public final Buffer flip() {
    limit = position;
    position = 0;
    mark = -1;
    return this;
}

However, in Java 11, ByteBuffer class has implemented its own flip() method, and the returning object is changed from Buffer to ByteBuffer (This change should be started from Java 9):

In ByteBuffer class:

ByteBuffer flip() {
    super.flip();
    return this;
}

Since I'm using JDK 11 (higher JDK version) to compile the program for running on Java 8, the mentioned exception would occasionally encountered accordingly to javadoc:

By default, however, javac compiles against the most-recent version of the platform APIs. The compiled program can therefore accidentally use APIs only available in the current version of the platform. Such programs cannot run on older versions of the platform, regardless of the values passed to the -source and -target options. This is a long-term usability pain point, since users expect that by using these options they'll get class files that can run on the the platform version specified by -target.

The statement could be referenced here: http://openjdk.java.net/jeps/247



So, for resolving this kind of problems, the are 2 ways to do so:


Approach 1

One can handle in the duration of compilation, by making use of a newly introduced command-line option:

i.e.
javac --release N <source files>

which is equals to:
for N < 9: -source N -target N -bootclasspath <documented-APIs-from-N>,  
for N >= 9: -source N -target N --system <documented-APIs-from-N>.  

Approach 2

Or we can handle it in codes, as precaution methods, by explicitly casting the ByteByffer as Buffer before calling corresponding methods:

((Buffer) bb).flip();

which in order to force it calling extended class's method (in case the compilation process hasn't taken the new command-line options into consideration):

private String loadFromFile(){
    
    RandomAccessFile inFile = null;
    FileChannel inChannel = null;
    StringBuilder sb = new StringBuilder();
    try {

        inFile = new RandomAccessFile(this.latestImageFile, "r");
        inChannel = inFile.getChannel();
        
        ByteBuffer bb = ByteBuffer.allocate(2046);
        while( inChannel.read(bb) != -1){
            ((Buffer)bb).flip(); // explicitly casting
            
            while(bb.hasRemaining()){
                char c = (char) bb.get();
                sb.append(c);
            }
            
            ((Buffer) bb).clear(); // explicitly casting
        }
        
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (inChannel != null) try {inChannel.close(); } catch (IOException e){}
        if (inFile != null ) try { inFile.close(); } catch (IOException e) {}
    }
    
    return sb.toString();
}
Mehdi LAMRANI
  • 11,289
  • 14
  • 88
  • 130
garykwwong
  • 2,327
  • 2
  • 16
  • 20
  • 7
    **Approach 3** would simply be to develop with the same jdk which is used for execution – Lino Apr 17 '20 at 08:48
  • 3
    That’s what the `--release` option is for. No need to specify these three different options yourself. That’s what the page you’ve linked describes. – Holger Apr 17 '20 at 21:59
  • 1
    I have converted a lot of projects to build with JDK 9+ (mostly 11) targeting runtime JVM of new and old (8 or lower) and the release flag is big improvement. The biggest improvement is you get compilation errors if you use a library/method that isn’t in your target JVM. I strongly recommend using it (option 1). Just in case it’s not clear how to do this with maven. You delete the source/target lines in the maven-compiler-plugin (use latest version) and replace them with a single “release” element instead specifying the target jvm you would like to run under (8 in your case it looks like). – Peter Roth Apr 18 '20 at 17:58
  • The `--release` option is not working with the Eclipse compiler (also available in IntelliJ), and I like the Eclipse compiler because you can compile code with syntax errors and it will generate bytecode that replaces your faulty code with code that throws the syntax error at runtime. – Mark Jeronimus May 06 '20 at 09:57
  • Thanks! I came across this post trying to resolve an error with the indexer in eclipse. I had eclipse pointing to a Java 8 build. I updated /etc/eclipse.ini to point to the Java 11 binary and it resolved my issue. – Aaron Swan Sep 10 '20 at 21:34
  • 1
    @Matthieu what do you mean? This code does compile with version 1.8. When you want to compile with a newer version but keep compatibility, use the `--release` option. That’s what it is for. Why does it have to be magic? – Holger Aug 25 '21 at 07:55
  • 1
    @MarkJeronimus Eclipse does support the `--release` option. The feature of generating class files even when there are errors, is entirely unrelated. – Holger Aug 25 '21 at 08:34
  • @Holger I (probably) meant "compile in Java 8 without the explicit casting and without the explicit `--release` flag". To be honest, this is yet another example of when at that time both God and I knew what I meant, and now only God knows ;) So I'm deleting my comment... – Matthieu Aug 25 '21 at 14:23
  • `--release` is nice, but some projects have to [turn this off](https://stackoverflow.com/q/56388086/3196753). – tresf Jan 30 '23 at 18:38
  • @garykwwong: Thank you for this write-up, so thorough! The casting was exactly what was needed for a project that can't use the `--release` flag. – tresf Jan 30 '23 at 18:49