Skip to content

Refactor Java Secret classes to align with Python SDK - #39806

Merged
shunping merged 2 commits into
apache:masterfrom
shunping:secret-manager-2
Aug 19, 2026
Merged

Refactor Java Secret classes to align with Python SDK#39806
shunping merged 2 commits into
apache:masterfrom
shunping:secret-manager-2

Conversation

@shunping

@shunping shunping commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

Description

Aligns Java SDK Secret management classes with the Python SDK (apache_beam.utils.secret).

Follow-up to #39636

Changes

  • Secret: Converted to an abstract class; added getBytes(boolean cacheSecret) in-memory caching, getString() accessors, and Secret.fromJson(...) factory methods.
  • RawSecret: Added implementation wrapping plaintext strings or byte arrays.
  • GcpSecret & GcpHsmGeneratedSecret: Added fromMap specification parsing, GCP project ID fallback resolution in GcpSecret, and equals/hashCode across all Secret classes.
  • Tests: Expanded unit tests in SecretTest.java covering JSON parsing, caching, option strings, and serialization.

@shunping shunping changed the title Support secret manager for jdbcio in python, java and yaml Refactor Java Secret classes to align with Python SDK Aug 18, 2026
Enhance Secret management interfaces and implementations in the Java SDK
to match the capabilities of apache_beam/utils/secret.py:

- Add RawSecret class to wrap raw byte arrays and string secrets directly.
- Add getBytes(boolean) and getString(boolean) default methods to Secret interface for cached retrieval.
- Add static fromJson factory method to Secret interface.
- Add in-memory caching and map specification parsing (fromMap) to GcpSecret and GcpHsmGeneratedSecret.
- Add GCP project ID fallback resolution in GcpSecret using environment variables (GOOGLE_CLOUD_PROJECT, GCP_PROJECT) and Application Default Credentials.
- Implement equals and hashCode across all Secret implementations.
@shunping
shunping marked this pull request as ready for review August 18, 2026 19:15
@shunping

Copy link
Copy Markdown
Collaborator Author

r: @damccorm

@shunping
shunping requested a review from damccorm August 18, 2026 19:16
@github-actions

Copy link
Copy Markdown
Contributor

Stopping reviewer notifications for this pull request: review requested by someone other than the bot, ceding control. If you'd like to restart, comment assign set of reviewers

@shunping

shunping commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator Author

This PR does not change the location of GcpSecret and GcpHsmGeneratedSecret: they were placed under sdks/java/core because they are used by GBEK. This added the dependency of gcp secret manager in https://github.com/apache/beam/blob/master/sdks/java/core/build.gradle#L110

However, sdks/java/core may not be the ideal place for all secret managers, especially if we want to support others like AWS or Azure later. For those, we may want to put them under their individual io folder/package: https://github.com/apache/beam/tree/master/sdks/java/io/amazon-web-services2/src/main/java/org/apache/beam/sdk/io/aws2 and https://github.com/apache/beam/tree/master/sdks/java/io/azure/src/main/java/org/apache/beam/sdk/io/azure.

For GCP secret manager, it is currently integrated in GBEK, but we may also move it to https://github.com/apache/beam/tree/master/sdks/java/extensions/google-cloud-platform-core/src/main/java/org/apache/beam/sdk/extensions/gcp in the future.

WDYT? @damccorm

@damccorm damccorm left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks - this mostly LGTM, just had a couple minor comments

"Invalid secret parameter " + String.join(", ", sortedInvalid));
}
return new GcpHsmGeneratedSecret(
Preconditions.checkNotNull(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need these precondition checks? Can these ever be null at this point?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I slightly change the validation, PTAL.

But yes, GcpHsmGeneratedSecret requires nonnull parameters.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, it was more to do with the duplicated validation (which I think is no longer being done)

Comment on lines +84 to +102
if (Strings.isNullOrEmpty(projectId)) {
projectId = System.getenv("GOOGLE_CLOUD_PROJECT");
}
if (Strings.isNullOrEmpty(projectId)) {
projectId = System.getenv("GCP_PROJECT");
}
if (Strings.isNullOrEmpty(projectId)) {
try {
Class<?> clazz = Class.forName("com.google.cloud.ServiceOptions");
java.lang.reflect.Method method = clazz.getMethod("getDefaultProjectId");
@SuppressWarnings("nullness")
Object result = method.invoke(null);
if (result != null) {
projectId = result.toString();
}
} catch (Throwable e) {
LOG.debug("Could not resolve GCP project via ServiceOptions reflection", e);
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're allowing this here, should we do the same for GcpHsmGeneratedSecret?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. I extracted that piece of code into a function and applied that in GcpHsmGeneratedSecret.

* should be able to return a valid byte array representing the secret.
*/
public interface Secret extends Serializable {
public abstract class Secret implements Serializable {

@shunping shunping Aug 18, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another way of doing this is to keep the Secret interface as not using cache, and then define a new abstract class CachedSecret.

This gives people flexibility to implement their Secret provider if they don't want cache in the first place, but it also adds another class layer of complexity.

It would be great to get a second opinion on this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like your current approach - it mirrors what we're doing in Python and separates the secret provider from the preferred client side behavior which is nice.

@damccorm

Copy link
Copy Markdown
Contributor

This PR does not change the location of GcpSecret and GcpHsmGeneratedSecret: they were placed under sdks/java/core because they are used by GBEK. This added the dependency of gcp secret manager in https://github.com/apache/beam/blob/master/sdks/java/core/build.gradle#L110

However, sdks/java/core may not be the ideal place for all secret managers, especially if we want to support others like AWS or Azure later. For those, we may want to put them under their individual io folder/package: https://github.com/apache/beam/tree/master/sdks/java/io/amazon-web-services2/src/main/java/org/apache/beam/sdk/io/aws2 and https://github.com/apache/beam/tree/master/sdks/java/io/azure/src/main/java/org/apache/beam/sdk/io/azure.

For GCP secret manager, it is currently integrated in GBEK, but we may also move it to https://github.com/apache/beam/tree/master/sdks/java/extensions/google-cloud-platform-core/src/main/java/org/apache/beam/sdk/extensions/gcp in the future.

WDYT? @damccorm

I think breaking them out makes sense. We could probably move the tests that depend on GcpSecret and GcpHsmGeneratedSecret out as well so that Secret is the only remaining thing with this dependency.

We'd need to modify

to allow classes to register secret managers (similar to how FileSystems work), but this seems doable

@shunping

Copy link
Copy Markdown
Collaborator Author

This PR does not change the location of GcpSecret and GcpHsmGeneratedSecret: they were placed under sdks/java/core because they are used by GBEK. This added the dependency of gcp secret manager in https://github.com/apache/beam/blob/master/sdks/java/core/build.gradle#L110
However, sdks/java/core may not be the ideal place for all secret managers, especially if we want to support others like AWS or Azure later. For those, we may want to put them under their individual io folder/package: https://github.com/apache/beam/tree/master/sdks/java/io/amazon-web-services2/src/main/java/org/apache/beam/sdk/io/aws2 and https://github.com/apache/beam/tree/master/sdks/java/io/azure/src/main/java/org/apache/beam/sdk/io/azure.
For GCP secret manager, it is currently integrated in GBEK, but we may also move it to https://github.com/apache/beam/tree/master/sdks/java/extensions/google-cloud-platform-core/src/main/java/org/apache/beam/sdk/extensions/gcp in the future.
WDYT? @damccorm

I think breaking them out makes sense. We could probably move the tests that depend on GcpSecret and GcpHsmGeneratedSecret out as well so that Secret is the only remaining thing with this dependency.

We'd need to modify

to allow classes to register secret managers (similar to how FileSystems work), but this seems doable

Sure. I will do the class moving (without functional changes) in a separate PR.

Agreed on the secret manager registration, but I think we can live without it in our first version. :)

@damccorm damccorm left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@codecov

codecov Bot commented Aug 18, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 65.40881% with 55 lines in your changes missing coverage. Please review.
✅ Project coverage is 58.14%. Comparing base (fde5698) to head (4bb5e35).
⚠️ Report is 10 commits behind head on master.

Files with missing lines Patch % Lines
.../main/java/org/apache/beam/sdk/util/GcpSecret.java 55.10% 19 Missing and 3 partials ⚠️
...src/main/java/org/apache/beam/sdk/util/Secret.java 70.49% 9 Missing and 9 partials ⚠️
...rg/apache/beam/sdk/util/GcpHsmGeneratedSecret.java 68.57% 5 Missing and 6 partials ⚠️
.../main/java/org/apache/beam/sdk/util/RawSecret.java 71.42% 1 Missing and 3 partials ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master   #39806      +/-   ##
============================================
- Coverage     58.48%   58.14%   -0.34%     
+ Complexity    14825    13135    -1690     
============================================
  Files          2737     2524     -213     
  Lines        274981   265686    -9295     
  Branches      12057    10824    -1233     
============================================
- Hits         160818   154493    -6325     
+ Misses       107890   105358    -2532     
+ Partials       6273     5835     -438     
Flag Coverage Δ
java 64.26% <65.40%> (-0.29%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@shunping
shunping merged commit f9b15b7 into apache:master Aug 19, 2026
31 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants