Skip to content

OpenRTB: Allow model inheritance from external packages - #4627

Open
pavel-ptashyts wants to merge 2 commits into
prebid:masterfrom
pavel-ptashyts:feature/openrtb-model-inheritance
Open

pavel-ptashyts wants to merge 2 commits into
prebid:masterfrom
pavel-ptashyts:feature/openrtb-model-inheritance

Conversation

@pavel-ptashyts

@pavel-ptashyts pavel-ptashyts commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

🔧 Type of changes

  • new feature
  • documentation

✨ What's the context?

When using Prebid Server Java as a JAR dependency, an application may reuse bidder adapters while keeping its own request and response representation. The adapter APIs accept concrete OpenRTB classes, so an unrelated wrapper cannot be passed to them. Most of these models are final and have package-private or private constructors, which prevents application-defined subclasses outside the model package.

This change allows those applications to extend the request and response models, including their nested OpenRTB and Native Ads objects, without maintaining a fork just to change class and constructor visibility. For example, an integration can attach local context to a response or define application-specific behavior in a subtype accepted by the existing APIs.

🧠 Rationale behind the change

Use @NonFinal on value classes and protected all-arguments constructors where constructors were inaccessible to external subclasses. Existing fields, builders and wire properties are retained. The SupplyChain, SupplyChainNode and Link factories remain available as explicit of(...) methods; BrandVersion keeps its existing public constructor. Native is already extensible and does not need a change.

This is a limited first step toward supporting external integrations. It does not implement lazy conversion, change tracking or builder inheritance, and does not claim an allocation reduction on its own. Inherited value fields remain private and final; applications are responsible for the behavior and state ownership of their subclasses. Subclass constructors may need updating as model fields evolve.

An alternative would be to migrate the other models to @SuperBuilder, following the existing Native implementation. That could provide a more complete foundation for inheritance with builders, but it is a broader change that needs additional implementation time and compatibility testing of the generated builder API. If maintainers prefer that direction, I am happy to follow up with a @SuperBuilder migration.

Subclass example and limitations

For example, a response subclass can initialize the inherited state from an existing response:

public class ApplicationBidResponse extends BidResponse {

    public ApplicationBidResponse(BidResponse response) {
        super(response.getId(), response.getSeatbid(), response.getBidid(), response.getCur(),
                response.getCustomdata(), response.getNbr(), response.getExt());
    }
}

Subclassing does not change the behavior of the generated builders: build() and toBuilder().build() produce the
declared model type, not the application's subtype. In particular, generated toBuilder() methods copy backing fields,
so overriding getters alone does not implement a lazy or copy-on-write proxy. Builder inheritance and change tracking
are outside the scope of this extension point. Custom accessors used by equality, such as Imp.bidFloor(), may also read
backing fields directly. Subclasses must account for this when overriding getters or equality.

Applications are responsible for their subclasses' serialization, equality and state ownership. Additional getters can
become JSON properties, and mutable collections or extension nodes are not made immutable by inheritance. Constructor
signatures follow the model fields, so subclasses may need to be updated when upgrading the dependency.

🧪 Test plan

  • mvn -B -Dtest=OpenRtbModelInheritanceTest test passes on JDK 25, including Checkstyle and compilation of production and test sources. The seven tests cover external-package request/response subclasses, equality, JSON round trips, toBuilder(), mutable native response models, retained factories and the public BrandVersion constructor.
  • Compiled an external-package subclass of every one of the 53 direct request/response models against the built classes. Compared their public method and constructor signatures with the baseline: all existing signatures are retained.
  • Full mvn -B test run: 8,785 tests, three failures in UidsCookieServiceTest (the same three tests also fail on the unchanged baseline), two skipped, and one environment-related SanityTest error because /var/tmp/vendor2 was not writable. SanityTest passes when rerun with vendor cache paths under target via system properties.

🏎 Quality check

  • Changes follow the code style guidelines.
  • Breaking changes to existing public construction APIs.
  • Test coverage exceeds 90%.
  • Erroneous console logs, debuggers or leftover code.

The project's JaCoCo configuration excludes com/iab/openrtb/**, so no coverage percentage is claimed for these models. The retained explicit factory methods are exercised by the regression tests.

@pavel-ptashyts

Copy link
Copy Markdown
Contributor Author

Hi @Net-burst and @CTMBNara could new review this PR.
Thanks in advance

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant