Skip to content

Conversation

@cbs228
Copy link
Contributor

@cbs228 cbs228 commented Nov 8, 2025

Description of Changes

Attempting to load a legacy NOAA Rapid Refresh GRIB2 file fails with a NullPointerException.

java -jar netcdfAll-5.10.0-SNAPSHOT.jar \
  rap_130_20170720_0000_000.grb2
stack trace
java.io.IOException: java.lang.NullPointerException: Cannot invoke "ucar.nc2.time.CalendarPeriod.millisecs()" because "from" is null
	at ucar.nc2.NetcdfFile.open(NetcdfFile.java:401)
	at ucar.nc2.dataset.NetcdfDataset.openProtocolOrFile(NetcdfDataset.java:837)
	at ucar.nc2.dataset.NetcdfDataset.openFile(NetcdfDataset.java:690)
	at ucar.nc2.NCdumpW.main(NCdumpW.java:723)
Caused by: java.lang.NullPointerException: Cannot invoke "ucar.nc2.time.CalendarPeriod.millisecs()" because "from" is null
	at ucar.nc2.time.CalendarPeriod.getConvertFactor(CalendarPeriod.java:197)
	at ucar.nc2.grib.grib2.table.Grib2Tables.getForecastTimeInterval(Grib2Tables.java:521)
	at ucar.nc2.grib.grib2.table.Grib2Tables.getForecastTimeIntervalOffset(Grib2Tables.java:621)
	at ucar.nc2.grib.collection.Grib2CollectionBuilder.filterIntervals(Grib2CollectionBuilder.java:180)
	at ucar.nc2.grib.collection.Grib2CollectionBuilder.makeGroups(Grib2CollectionBuilder.java:116)
	at ucar.nc2.grib.collection.GribCollectionBuilder.createMultipleRuntimeCollections(GribCollectionBuilder.java:128)
	at ucar.nc2.grib.collection.GribCollectionBuilder.createIndex(GribCollectionBuilder.java:120)
	at ucar.nc2.grib.collection.GribCdmIndex.openGribCollectionFromDataFile(GribCdmIndex.java:825)
	at ucar.nc2.grib.collection.GribCdmIndex.openGribCollectionFromDataFile(GribCdmIndex.java:804)
	at ucar.nc2.grib.collection.GribCdmIndex.openGribCollectionFromRaf(GribCdmIndex.java:774)
	at ucar.nc2.grib.collection.GribIosp.open(GribIosp.java:201)
	at ucar.nc2.NetcdfFile.<init>(NetcdfFile.java:1610)
	at ucar.nc2.NetcdfFile.open(NetcdfFile.java:798)
	at ucar.nc2.NetcdfFile.open(NetcdfFile.java:398)
	... 3 more

This file loads correctly in netcdf-java 4.6, but it has a quirk: it contains at least one PDS that does not define a time range.

grib_dump -O -p section_4 \
  -w section4Length=58,numberOfTimeRange=0 \
  rap_130_20170720_0000_000.grb2
grib_dump output
***** FILE: rap_130_20170720_0000_000.grb2 
======================   SECTION_4 ( length=58, padding=12 )   ======================
1-4       section4Length = 58
5         numberOfSection = 4
6-7       NV = 0
8-9       productDefinitionTemplateNumber = 8 [Average, accumulation, extreme values or other statistically processed values at a horizontal level or in a horizontal layer in a continuous or non-continuous time interval (grib2/tables/2/4.0.table) ]
10        parameterCategory = 1 [Moisture (grib2/tables/2/4.1.0.table) ]
11        parameterNumber = 29 [Total snowfall (m)  (grib2/tables/2/4.2.0.1.table) ]
12        typeOfGeneratingProcess = 2 [Forecast (grib2/tables/2/4.3.table) ]
13        backgroundProcess = 0
14        generatingProcessIdentifier = 105
15-16     hoursAfterDataCutoff = 0
17        minutesAfterDataCutoff = 0
18        indicatorOfUnitOfTimeRange = 1 [Hour (grib2/tables/2/4.4.table) ]
19-22     forecastTime = 0
23        typeOfFirstFixedSurface = 1 [Ground or water surface (grib2/tables/2/4.5.table , grib2/tables/local/kwbc/1/4.5.table) ]
24        scaleFactorOfFirstFixedSurface = 0
25-28     scaledValueOfFirstFixedSurface = 0
29        typeOfSecondFixedSurface = 255 [Missing (grib2/tables/2/4.5.table , grib2/tables/local/kwbc/1/4.5.table) ]
30        scaleFactorOfSecondFixedSurface = 0
31-34     scaledValueOfSecondFixedSurface = 0
35-36     yearOfEndOfOverallTimeInterval = 2017
37        monthOfEndOfOverallTimeInterval = 7
38        dayOfEndOfOverallTimeInterval = 20
39        hourOfEndOfOverallTimeInterval = 0
40        minuteOfEndOfOverallTimeInterval = 0
41        secondOfEndOfOverallTimeInterval = 0

42        numberOfTimeRange = 0  <───── QUIRK

43-46     numberOfMissingInStatisticalProcess = 0

6bef2fb (Fix for issue #606, version 5., 2021-02-26) corrects time calculations for GRIB2 PDS which have incompatible units for time intervals and related time quantities. The above patch introduces a TimeIntervalAndUnits parser for PDS times.

If a PDS does not define any time intervals, the previous version of

Grib2Tables.getForecastTimeInterval(Grib2Record)

returns a duration of zero—i.e., no elapsed time. This is likely the correct interpretation if no time intervals are present. After 6bef2fb, this method raises a NullPointerException instead.

This is because the inner function

Grib2Tables.getForecastTimeInterval(Grib2Pds.PdsInterval)

returns an invalid TimeIntervalAndUnits when it ought to return a zero duration instead. The calling functions do not treat this data type as fallible and may use it in conversions that return null.

If the PDS does not contain any time intervals, return a "zero hour" duration instead. This restores the previous behavior.

  • In legacy and modern RAP models, PDS which do define time intervals define them as zero. The interpretation of the "quirky" PDS remains consistent with its brethren.

  • This quirk is not present in more recent RAP model runs, so this bug is unlikely to affect current RAP data.

PR Checklist

  • Link to any issues that the PR addresses
  • Add labels
  • Open as a draft PR
    until ready for review
  • Make sure GitHub tests pass
  • Mark PR as "Ready for Review"

Attempting to load a [legacy] NOAA Rapid Refresh GRIB2 file fails
with a NullPointerException.

    java -jar netcdfAll-5.10.0-SNAPSHOT.jar \
      rap_130_20170720_0000_000.grb2

This file loads correctly in netcdf-java 4.6, but it has a quirk:
it contains at least one PDS that does not define a time range.

    grib_dump -O -p section_4 \
      -w section4Length=58,numberOfTimeRange=0 \
      rap_130_20170720_0000_000.grb2

6bef2fb (Fix for issue #606, version 5., 2021-02-26) corrects
time calculations for GRIB2 PDS which have incompatible units for
**time intervals** and related time quantities. The above patch
introduces a `TimeIntervalAndUnits` parser for PDS times.

If a PDS does not define *any* time intervals, the previous
version of

    Grib2Tables.getForecastTimeInterval(Grib2Record)

returns a duration of zero—i.e., no elapsed time. This is likely
the correct interpretation if no time intervals are present. After
6bef2fb, this method raises a NullPointerException instead.

This is because the inner function

    Grib2Tables.getForecastTimeInterval(Grib2Pds.PdsInterval)

returns an invalid `TimeIntervalAndUnits` when it ought to return
a zero duration instead. The calling functions do not treat this
data type as fallible and may use it in conversions that
return null.

If the PDS does not contain any time intervals, return a
"zero hour" duration instead. This restores the previous behavior.

* In legacy and modern RAP models, PDS which *do* define time
  intervals define them as zero. The interpretation of the
  "quirky" PDS remains consistent with its brethren.

* This quirk is not present in more [recent] RAP model runs, so
  this bug is unlikely to affect current RAP data.

[legacy]: https://www.ncei.noaa.gov/oa/prod-model/rapid-refresh/access/historical/analysis/201707/20170720/rap_130_20170720_0000_000.grb2

[recent]: https://www.ncei.noaa.gov/oa/prod-model/rapid-refresh/access/rap-130-13km/analysis/202504/20250401/rap_130_20250401_0000_000.grb2
@CLAassistant
Copy link

CLAassistant commented Nov 8, 2025

CLA assistant check
All committers have signed the CLA.

@cbs228
Copy link
Contributor Author

cbs228 commented Nov 8, 2025

This passes CI on my fork, so hopefully it will pass here as well. Ready for review.

@cbs228 cbs228 marked this pull request as ready for review November 8, 2025 21:12
@cbs228 cbs228 requested a review from lesserwhirls as a code owner November 8, 2025 21:12
@lesserwhirls lesserwhirls added bug Something isn't working grib2 labels Nov 10, 2025
@lesserwhirls lesserwhirls added this to the 5.10.0 milestone Nov 10, 2025
@lesserwhirls
Copy link
Member

Thank you so much for your contribution @cbs228! I ran our internal integration tests against your changes as well, and everything passes 👍🏻

@lesserwhirls lesserwhirls merged commit a5ead0b into Unidata:maint-5.x Nov 10, 2025
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working grib2

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants