It should be sufficient to just correct the typo where you forgot to terminate the string literal inside the JSON_EXISTS function call and then the ? will be inside a string and JDBC will not parse it as a bind parameter.
If for some reason correcting the typo is insufficient then:
From the OJDBC documentation (which is subtly different from the documentation linked from the question):
A.4.4 MATCH_RECOGNIZE Clause
The ? character is used as a token in MATCH_RECOGNIZE clause in Oracle Database 11g and later versions. As the JDBC standard defines the ? character as a parameter marker, the JDBC Driver and the Server SQL Engine cannot distinguish between different uses of the same token.
In earlier versions of JDBC Driver, if you want to interpret the ? character as a MATCH_RECOGNIZE token and not as a parameter marker, then you must use a Statement instead of a PreparedStatement and disable escape processing. However, starting from Oracle Database 12c Release 1 (12.1.0.2), you can use the '{\\ ... \\}' syntax while using the ? character, so that the JDBC driver does not process it as a parameter marker and allows the SQL engine to process it.
Following that, I think your code (correcting for the typo where you do not close the string literal and are missing a closing bracket in the JSON path expression) should be:
String sql =
      "SELECT * "
    + "FROM   TEST_JSON2 "
    + "WHERE  JSON_EXISTS(json_data, {\\'$?(@[*] == ?)'\\})";
PreparedStatement ps = conn.prepareStatatement(sql);
ResultSet rs = ps.executeQuery();
or, to not use a PreparedStatement and just use a Statement:
String sql =
      "SELECT * "
    + "FROM   TEST_JSON2 "
    + "WHERE  JSON_EXISTS(json_data, '$?(@[*] == ?)')";
Statement s = conn.createStatatement(sql);
ResultSet rs = s.executeQuery();