Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
b72276a
Add multi-release jars to enable Java Modules
yschimke May 11, 2025
ab042de
Fix android tests
yschimke May 11, 2025
879bf1a
document todo
yschimke May 11, 2025
69df152
Validate more modules
yschimke May 11, 2025
ac24e72
Fix
yschimke May 11, 2025
ce79429
Add a test module compiling using Java modules
yschimke May 11, 2025
fe7e549
Avoid running module tests on JDK 8
yschimke May 11, 2025
a828dc7
Add a jlink run task to demonstrate java modules
yschimke May 11, 2025
14e6e16
Merge branch 'master' into mrjar
yschimke May 26, 2025
b268038
Merge branch 'master' into mrjar
yschimke May 29, 2025
71057d1
Merge branch 'master' into mrjar
yschimke Jun 16, 2025
f1a2f03
Fix version
yschimke Jun 16, 2025
c56f1e6
Merge branch 'refs/heads/master' into mrjar
yschimke Jun 21, 2025
aca5013
Update
yschimke Jun 21, 2025
8b49e10
Merge remote-tracking branch 'yschimke/mrjar' into mrjar
yschimke Jun 21, 2025
0973e8d
Remove old comment
yschimke Jun 21, 2025
2cf11d3
Merge branch 'master' into mrjar
yschimke Jul 12, 2025
1ba44f1
Update module test
yschimke Jul 12, 2025
7eef165
Merge branch 'master' into mrjar
yschimke Jul 19, 2025
6a3a64b
cleanup and document
yschimke Jul 19, 2025
2b452f7
suppress module warnings
yschimke Jul 19, 2025
220529b
add
yschimke Jul 19, 2025
c2bb349
add
yschimke Jul 19, 2025
1488a1b
readme
yschimke Jul 19, 2025
b028111
add more modules
yschimke Jul 19, 2025
fb8636d
Fix
yschimke Jul 19, 2025
45ec46b
Run with Jlink
yschimke Jul 19, 2025
d9d121f
Add mockwebserver test
yschimke Jul 19, 2025
2398c5a
Fix build
yschimke Jul 19, 2025
c6bf693
Fix build
yschimke Jul 19, 2025
16cb3a2
Merge branch 'master' into mrjar
yschimke Jul 19, 2025
17f6354
Merge branch 'master' into mrjar
yschimke Jul 27, 2025
473ce2c
Merge branch 'master' into mrjar
yschimke Aug 10, 2025
9a6f004
Merge branch 'master' into mrjar
yschimke Aug 16, 2025
cb0dedd
Fix build
yschimke Aug 16, 2025
37161cd
Merge branch 'master' into mrjar
yschimke Aug 22, 2025
d9c1199
Merge branch 'master' into mrjar
yschimke Sep 20, 2025
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
20 changes: 20 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -704,3 +704,23 @@ jobs:
- name: Run maven test
working-directory: ./maven-tests
run: ./mvnw -q verify

test_java_modules:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Configure JDK
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: 24

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4

- name: Run with Jlink
run: ./gradlew module-tests:imageRun

30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,36 @@ $ ./gradlew okcurl:nativeImage
$ ./okcurl/build/graal/okcurl https://httpbin.org/get
```

Java Modules
------------

OkHttp (5.3+) implements Java 9 Modules.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What the heck is OkHttp 5.3 ?! We have to ship 5.2 first!

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.

Ooops, can fix.


With this in place Java builds should fail if apps attempt to use internal packages.

```
error: package okhttp3.internal.platform is not visible
okhttp3.internal.platform.Platform.get();
^
(package okhttp3.internal.platform is declared in module okhttp3,
which does not export it to module i.am.bad.and.i.should.feel.bad)
Comment on lines +225 to +229

@yschimke yschimke Jul 19, 2025

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.

@swankjesse I guess this is the main 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.

Also added a CI test that jlink works, see https://www.baeldung.com/jlink

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.

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.

@swankjesse do you need more convincing?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is good behavior. I wouldn’t use i.am.bad.and.i.should.feel.bad as the example; this is not a place for shame

```

The stable public API is based on the list of defined modules:

- okhttp3
- okhttp3.brotli
- okhttp3.coroutines
- okhttp3.dnsoverhttps
- okhttp3.java.net.cookiejar
- okhttp3.logging
- okhttp3.sse
- okhttp3.tls
- okhttp3.urlconnection
- mockwebserver3
- mockwebserver3.junit4
- mockwebserver3.junit5

License
-------

Expand Down
3 changes: 2 additions & 1 deletion android-test/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ android {
"META-INF/LICENSE.md",
"META-INF/LICENSE-notice.md",
"README.txt",
"org/bouncycastle/LICENSE"
"org/bouncycastle/LICENSE",
"META-INF/versions/9/OSGI-INF/MANIFEST.MF"
Comment thread
yschimke marked this conversation as resolved.
)
}

Expand Down
10 changes: 8 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ subprojects {
if (project.name == "regression-test") return@subprojects
if (project.name == "android-test-app") return@subprojects
if (project.name == "container-tests") return@subprojects
if (project.name == "module-tests") return@subprojects

apply(plugin = "checkstyle")
apply(plugin = "ru.vyarus.animalsniffer")
Expand Down Expand Up @@ -243,8 +244,13 @@ subprojects {
}

tasks.withType<JavaCompile> {
sourceCompatibility = projectJavaVersion.toString()
targetCompatibility = projectJavaVersion.toString()
if (name.contains("Java9")) {
sourceCompatibility = "9"
targetCompatibility = "9"
} else {
sourceCompatibility = projectJavaVersion.toString()
targetCompatibility = projectJavaVersion.toString()
}
}
}

Expand Down
11 changes: 6 additions & 5 deletions buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@ plugins {

repositories {
mavenCentral()
gradlePluginPortal()
}

dependencies {
// TODO (https://github.com/square/okhttp/issues/8612) we will need a consistent version
// 7.1.0 is used because it avoids this error
// Could not create an instance of type aQute.bnd.gradle.BundleTaskExtension.
// Cannot change attributes of configuration ':native-image-tests:compileClasspath' after it has been locked for mutation
implementation("biz.aQute.bnd:biz.aQute.bnd.gradle:7.1.0")
implementation(libs.gradlePlugin.bnd)

implementation(libs.kotlin.gradle.plugin.api)

implementation(libs.gradlePlugin.mrjar)
}
8 changes: 8 additions & 0 deletions buildSrc/settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -1 +1,9 @@
rootProject.name = "okhttp-buildSrc"

dependencyResolutionManagement {
versionCatalogs {
create("libs") {
from(files("../gradle/libs.versions.toml"))
}
}
}
59 changes: 59 additions & 0 deletions buildSrc/src/main/kotlin/JavaModules.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright (C) 2025 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import me.champeau.mrjar.MultiReleaseExtension
import org.gradle.api.Project
import org.gradle.api.tasks.compile.JavaCompile
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.named
import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile

fun Project.applyJavaModules(
moduleName: String,
defaultVersion: Int = 8,
javaModuleVersion: Int = 9,
enableValidation: Boolean = true,
) {
plugins.apply("me.champeau.mrjar")
Comment thread
yschimke marked this conversation as resolved.

configure<MultiReleaseExtension> {
targetVersions(defaultVersion, javaModuleVersion)
}

tasks.named<JavaCompile>("compileJava9Java").configure {
val compileKotlinTask = tasks.getByName("compileKotlin") as KotlinJvmCompile
dependsOn(compileKotlinTask)

if (enableValidation) {
compileKotlinTask.source(file("src/main/java9"))
}

// Ignore warnings about using 'requires transitive' on automatic modules.
// not needed when compiling with recent JDKs, e.g. 17
options.compilerArgs.add("-Xlint:-requires-transitive-automatic")
Comment thread
yschimke marked this conversation as resolved.

// Patch the compileKotlinJvm output classes into the compilation so exporting packages works correctly.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Very helpful comment!

options.compilerArgs.addAll(
listOf(
"--patch-module",
"$moduleName=${compileKotlinTask.destinationDirectory.get().asFile}",
),
)

classpath = compileKotlinTask.libraries
modularity.inferModulePath.set(true)
}
}
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ gradlePlugin-kotlinSerialization = { module = "org.jetbrains.kotlin:kotlin-seria
gradlePlugin-ksp = { module = "com.google.devtools.ksp:symbol-processing-gradle-plugin", version.ref = "ksp" }
gradlePlugin-mavenPublish = "com.vanniktech:gradle-maven-publish-plugin:0.34.0"
gradlePlugin-mavenSympathy = "io.github.usefulness.maven-sympathy:io.github.usefulness.maven-sympathy.gradle.plugin:0.3.0"
gradlePlugin-mrjar = "me.champeau.mrjar:me.champeau.mrjar.gradle.plugin:0.1.1"
gradlePlugin-shadow = "com.gradleup.shadow:shadow-gradle-plugin:9.1.0"
gradlePlugin-spotless = "com.diffplug.spotless:spotless-plugin-gradle:7.2.1"
hamcrestLibrary = "org.hamcrest:hamcrest-library:3.0"
Expand All @@ -82,6 +83,7 @@ junit-vintage-engine = { module = "org.junit.vintage:junit-vintage-engine", vers
junit-pioneer = "org.junit-pioneer:junit-pioneer:1.9.1"
junit5android-core = { module = "de.mannodermaus.junit5:android-test-core", version.ref = "de-mannodermaus-junit5" }
junit5android-runner = { module = "de.mannodermaus.junit5:android-test-runner", version.ref = "de-mannodermaus-junit5" }
kotlin-gradle-plugin-api = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin-api", version.ref = "org-jetbrains-kotlin" }
kotlin-junit5 = { module = "org.jetbrains.kotlin:kotlin-test-junit5", version.ref = "org-jetbrains-kotlin" }
kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "org-jetbrains-kotlin" }
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "org-jetbrains-kotlin" }
Expand Down
6 changes: 1 addition & 5 deletions mockwebserver-deprecated/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,7 @@ plugins {
id("binary-compatibility-validator")
}

tasks.jar {
manifest {
attributes("Automatic-Module-Name" to "okhttp3.mockwebserver")
}
}
project.applyJavaModules("okhttp3.mockwebserver")

dependencies {
"friendsApi"(projects.okhttp)
Expand Down
6 changes: 6 additions & 0 deletions mockwebserver-deprecated/src/main/java9/module-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@SuppressWarnings("module")
module okhttp3.mockwebserver {
requires okhttp3;
exports okhttp3.mockwebserver;
requires java.logging;
}
Comment thread
yschimke marked this conversation as resolved.
6 changes: 1 addition & 5 deletions mockwebserver-junit4/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,7 @@ plugins {
id("binary-compatibility-validator")
}

tasks.jar {
manifest {
attributes("Automatic-Module-Name" to "mockwebserver3.junit4")
}
}
project.applyJavaModules("mockwebserver3.junit4")

dependencies {
api(projects.okhttp)
Expand Down
6 changes: 6 additions & 0 deletions mockwebserver-junit4/src/main/java9/module-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@SuppressWarnings("module")
module mockwebserver3.junit4 {
requires okhttp3;
exports mockwebserver3.junit4;
requires java.logging;
}
7 changes: 2 additions & 5 deletions mockwebserver-junit5/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,9 @@ plugins {
id("binary-compatibility-validator")
}

project.applyJavaModules("mockwebserver3.junit5")

tasks {
jar {
manifest {
attributes("Automatic-Module-Name" to "mockwebserver3.junit5")
}
}
test {
useJUnitPlatform()
}
Expand Down
5 changes: 5 additions & 0 deletions mockwebserver-junit5/src/main/java9/module-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@SuppressWarnings("module")
module mockwebserver3.junit5 {
requires okhttp3;
opens mockwebserver3.junit5.internal;
}
6 changes: 1 addition & 5 deletions mockwebserver/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,7 @@ plugins {
id("binary-compatibility-validator")
}

tasks.jar {
manifest {
attributes("Automatic-Module-Name" to "mockwebserver3")
}
}
project.applyJavaModules("mockwebserver3")

dependencies {
"friendsApi"(projects.okhttp)
Expand Down
6 changes: 6 additions & 0 deletions mockwebserver/src/main/java9/module-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@SuppressWarnings("module")
module mockwebserver3 {
requires okhttp3;
exports mockwebserver3;
requires java.logging;
}
67 changes: 67 additions & 0 deletions module-tests/build.gradle.kts
Comment thread
yschimke marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
plugins {
id("java")
id("application")
id("com.github.iherasymenko.jlink") version "0.7"
id("org.gradlex.extra-java-module-info") version "1.13"
}

dependencies {
implementation(projects.okhttp)
implementation(projects.loggingInterceptor)

testImplementation(projects.okhttp)
testImplementation(projects.loggingInterceptor)
testImplementation(projects.mockwebserver3)
testImplementation(projects.mockwebserver3Junit5)

testImplementation(libs.junit.jupiter.api)
testRuntimeOnly(libs.junit.jupiter.engine)
testRuntimeOnly(libs.junit.platform.launcher)
}

application {
mainClass = "okhttp3.modules.Main"
Comment thread
yschimke marked this conversation as resolved.
mainModule = "okhttp3.modules"
}

jlinkApplication {
stripDebug = true
stripJavaDebugAttributes = true
compress.set("zip-9")
addModules.addAll("jdk.crypto.ec", "java.logging")
vm.set("server")
}

extraJavaModuleInfo {
module("org.jetbrains:annotations", "org.jetbrains.annotations") {
exportAllPackages()
}
module("com.squareup.okio:okio-jvm", "okio") {
exportAllPackages()
requires("kotlin.stdlib")
requires("java.logging")
}
module("com.squareup.okio:okio", "okio") {
exportAllPackages()
}
}

val testJavaVersion = System.getProperty("test.java.version", "21").toInt()

tasks.withType<Test> {
useJUnitPlatform()

enabled = testJavaVersion > 8

javaLauncher.set(javaToolchains.launcherFor {
languageVersion.set(JavaLanguageVersion.of(testJavaVersion))
})
}

java {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
toolchain {
languageVersion.set(JavaLanguageVersion.of(21))
}
}
7 changes: 7 additions & 0 deletions module-tests/src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@SuppressWarnings("module")
module okhttp3.modules {
requires okhttp3;
requires okhttp3.logging;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

what the heck is okhttp3.logging ?!

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.

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.

#3743 (comment)

I think I misread your comment.

image

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.

Or maybe favoured Stephen's comment over yours?

Your module name should be the same as the package name in that jar file, unless you have a good reason otherwise. ie. you as a human are deciding that one package is the base, and assigning that as the module name.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Yep yep, it’s the package name. I missed that we gave that module two different names.

requires jdk.crypto.ec;
exports okhttp3.modules;
}
30 changes: 30 additions & 0 deletions module-tests/src/main/java/okhttp3/modules/Main.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright (C) 2025 Block, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package okhttp3.modules;

import okhttp3.Call;
import okhttp3.HttpUrl;

import java.io.IOException;

public class Main {
public static void main(String[] args) throws IOException {
Call call = OkHttpCaller.callOkHttp(HttpUrl.get("https://square.com/robots.txt"));

System.out.println(call.execute().body().string());
}
}
Loading
Loading