Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 1998-2025 John Caron and University Corporation for Atmospheric Research/Unidata
* Copyright (c) 1998-2026 John Caron and University Corporation for Atmospheric Research/Unidata
* See LICENSE for license information.
*/

Expand Down Expand Up @@ -282,20 +282,27 @@ public static void read(org.jdom2.Element root, StringBuilder errlog) {
String name = elem.getChildText("libraryName");
errlog.append(
String.format("Netcdf4Clibrary from NJ22CONFIG: libraryPath = '%s', libraryName = '%s' \n", path, name));

Element useForReadingElm = elem.getChild("useForReading");
boolean useForReading = Boolean.parseBoolean(useForReadingElm.getText());
errlog.append(String.format(" useForReading = '%s' \n", useForReading));

boolean strict = Boolean.parseBoolean(useForReadingElm.getAttributeValue("strict"));
errlog.append(String.format(" strictReader = '%s' \n", strict));

if (path != null && name != null) {
// reflection is used to decouple optional jars
try {
Class<?> netcdfClibraryClass =
RuntimeConfigParser.class.getClassLoader().loadClass(netcdfClibraryClassName);
Method method = netcdfClibraryClass.getMethod("setLibraryNameAndPath", String.class, String.class);
method.invoke(null, path, name); // static method has null for object
Method method =
netcdfClibraryClass.getMethod("setLibraryNameAndPath", String.class, String.class, boolean.class);
method.invoke(null, path, name, strict); // static method has null for object
} catch (Throwable e) {
errlog.append(netcdfClibraryClassName + " is not on classpath\n");
}
}

boolean useForReading = Boolean.parseBoolean(elem.getChildText("useForReading"));
errlog.append(String.format(" useForReading = '%s' \n", useForReading));
if (useForReading) {
try {
// Registers Nc4Iosp in front of all the other IOSPs already registered in NetcdfFile.<clinit>().
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Runtime loading
last_updated: 2025-08-15
last_updated: 2026-06-16
sidebar: netcdfJavaTutorial_sidebar
permalink: runtime_loading.html
toc: false
Expand Down Expand Up @@ -122,7 +122,7 @@ The configuration file looks like this:
<Netcdf4Clibrary>{% raw %}{% annotation 9 %}{% endraw %}
<libraryPath>/usr/local/lib</libraryPath>
<libraryName>netcdf</libraryName>
<useForReading>false</useForReading>
<useForReading strict="false">false</useForReading>
</Netcdf4Clibrary>
</runtimeConfig>
{% endhighlight_with_annotations %}
Expand All @@ -138,8 +138,10 @@ The configuration file looks like this:
* {% annotation 9 %} Configure how the [NetCDF-4 C library](netcdf4_c_library.html) is discovered and used.
* `libraryPath`: The directory in which the native library is installed.
* `libraryName`: The name of the native library. This will be used to locate the proper `.DLL`, `.SO`, or `.DYLIB` file within the `libraryPath` directory.
* `useForReading`: By default, the native library is only used for writing NetCDF-4 files; a pure-Java layer is responsible for reading them.
However, if this property is set to `true`, then it will be used for reading NetCDF-4 (and HDF5) files as well.
* `useForReading`: By default, the native library is only used for writing NetCDF-4 files; a pure-Java layer is responsible for reading them.
However, if this property is set to `true`, then the native library will be used for reading.
When enabled, all HDF5 files will be read by the native netCDF-C library.
However, if the strict attribute on the `useForReading` element is set to `true`, then the netCDF-C library will only be used to read in files that are highly likely to be netCDF-4 files (vanilla HDF5 files will still be read by the pure-Java layer).

There are several ways pass the Runtime Configuration XML to the CDM library. From your application, you can pass a `java.io.InputStream` (or JDOM element) to
`ucar.nc2.util.xml.RuntimeConfigParser`, as in the following examples:
Expand Down
27 changes: 26 additions & 1 deletion netcdf4/src/main/java/ucar/nc2/ffi/netcdf/NetcdfClibrary.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 1998-2021 John Caron and University Corporation for Atmospheric Research/Unidata
* Copyright (c) 1998-2026 John Caron and University Corporation for Atmospheric Research/Unidata
* See LICENSE for license information.
*/

Expand Down Expand Up @@ -32,6 +32,8 @@ public class NetcdfClibrary {
// Track if already tested for library presence.
private static Boolean isClibraryPresent;

private static boolean strictRead = false;

/**
* Set the path and name of the netcdf c library.
* <p>
Expand All @@ -45,7 +47,25 @@ public class NetcdfClibrary {
* @param lib_name library name, may be null. If null, will use "netcdf".
*/
public static void setLibraryNameAndPath(@Nullable String jna_path, @Nullable String lib_name) {
setLibraryNameAndPath(jna_path, lib_name, false);
}

/**
* Set the path and name of the netcdf c library.
* <p>
* Must be called prior to calling {@link #isLibraryPresent() isLibraryPresent}
* or {@link #getForeignFunctionInterface() getForeignFunctionInterface}, as
* the C library can only be successfully loaded once.
*
* @param jna_path path to shared libraries, may be null. If null, will look for system property
* "jna.library.path", then environment variable "JNA_PATH". If set, will set
* the environment variable "JNA_PATH".
* @param lib_name library name, may be null. If null, will use "netcdf".
* @param strict if true, only attempt to read files through the netCDF-C library that are likely
* netCDF-4 files, not just any HDF5 file.
*
*/
public static void setLibraryNameAndPath(@Nullable String jna_path, @Nullable String lib_name, boolean strict) {
if (nc4 != null) {
log.warn("netCDF-C library already set, ignoring.");
return;
Expand All @@ -72,6 +92,7 @@ public static void setLibraryNameAndPath(@Nullable String jna_path, @Nullable St

libName = lib_name;
jnaPath = jna_path;
strictRead = strict;

if ((isClibraryPresent == null || !isClibraryPresent) && jnaPath != null) {
// call load to retry loading, but this time with jnaPath set
Expand Down Expand Up @@ -145,6 +166,10 @@ public static synchronized void shutdown() {
}
}

public static boolean isStrictRead() {
return strictRead;
}

private static Nc4prototypes load() {
if (nc4 == null) {
if (jnaPath == null) {
Expand Down
56 changes: 47 additions & 9 deletions netcdf4/src/main/java/ucar/nc2/jni/netcdf/Nc4Iosp.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 1998-2025 University Corporation for Atmospheric Research/Unidata
* Copyright (c) 1998-2026 University Corporation for Atmospheric Research/Unidata
* See LICENSE for license information.
*/

Expand Down Expand Up @@ -132,6 +132,36 @@ public static void useHdfEos(boolean val) {

public static void setDebugFlags(DebugFlags flags) {}

private static boolean isLikelyNetcdf4(RandomAccessFile raf) {
boolean likelyNc4 = false;
// Check if likely netCDF-4 file (to prevent reading vanilla HDF5 through the netCDF-C library)
// See https://github.com/Unidata/netcdf-c/discussions/3085
// Note: netCDF-4 files written outside the netCDF-C library may not pass these checks.
IntByReference test_ncid = new IntByReference();
int ret = nc4.nc_open(raf.getLocation(), NC_NOWRITE, test_ncid);
SizeTByReference lenp = new SizeTByReference();
// Does _NCProperties global attribute exist?
ret = nc4.nc_inq_attlen(test_ncid.getValue(), NC_GLOBAL, "_NCCCCC", lenp);
// ret = nc4.nc_inq_attlen(test_ncid.getValue(), NC_GLOBAL, CDM.NCPROPERTIES, lenp);
likelyNc4 = ret == NC_NOERR;
if (!likelyNc4) {
// Does _IsNetcdf4 global attribute exist, and is its value not 0?
// note: newer versions of netCDF-C include the _NCProperties check as part of determining
// the value of this attribute, but not all, so we need both checks.
lenp = new SizeTByReference();
ret = nc4.nc_inq_attlen(test_ncid.getValue(), NC_GLOBAL, CDM.ISNETCDF4, lenp);
if (ret == NC_NOERR && lenp.getValue().longValue() == 1) {
int[] isnc4 = new int[1];
ret = nc4.nc_get_att_int(test_ncid.getValue(), NC_GLOBAL, CDM.ISNETCDF4, isnc4);
likelyNc4 = ret == NC_NOERR && isnc4[0] != 0;
}
}
if (!likelyNc4) {
log.debug("May not be a netCDF-4 file: {}; falling back to HDF5 IOSP.", raf.getLocation());
}
return likelyNc4;
}

//////////////////////////////////////////////////
// Instance Variables

Expand Down Expand Up @@ -177,24 +207,32 @@ public void setChunker(Nc4Chunking chunker) {
@Override
public boolean isValidFile(RandomAccessFile raf) throws IOException {
int format = NCheader.checkFileType(raf);
boolean valid = false;
boolean validCheck1 = false;
switch (format) {
case NCheader.NC_FORMAT_NETCDF4:
case NCheader.NC_FORMAT_64BIT_DATA:
valid = true;
validCheck1 = true;
break;
default:
break;// everything else is invalid
}
if (valid) {
if (isClibraryPresent()) {
return true;

boolean validCheck2 = false;
if (!validCheck1) {
log.debug("File cannot be opened by Nc4Iosp: {}", raf.getLocation());
} else if (!isClibraryPresent()) {
log.debug("File appears to be valid but netCDF-C isn't installed: {}", raf.getLocation());
} else {
// file appears to be valid and netCDF-c is present
if (NetcdfClibrary.isStrictRead()) {
// strictly limit to reading files that are very likely netcdf4
validCheck2 = isLikelyNetcdf4(raf);
} else {
log.debug("File is valid but the NetCDF-4 native library isn't installed: {}", raf.getLocation());
// try to read all HDF5 files through the netCDF-C library
validCheck2 = true;
}
}

return false;
return validCheck2;
}

// 2016-06-06 note: Once netcdf-c v4.4.1 is released, we should be able to return much better information from
Expand Down