Repository: mockito/mockito
Issue: Release 2.1.0 final
Labels: epic

The purpose of this ticket is to collect all actions and requirements for getting 2.1.0 final. Let's not wait too long :)
- [x] investigate #638 
- [x] identify opportunities to promo the RC and get more feedback
- [x] decide how long do we wait for feedback before publishing final version
- [x] @TimvdLippe review the #648
- [x] #648 - include more info the javadoc, e.g. direction of the feature, what we expect from the user, inform it is incubating, etc
- [x] @szczepiq review the #648
- update the release notes re #648
  - [x] inform about the new feature (incubating, motivating, future direction)
- [x] publish another RC and spread the word


Issue comments:
I think we can push `2.1.0` next week since RC has been available for a little while now. Also I submitted a (random) PR to Google Guava and 850k+ tests did not fail :tada: https://github.com/google/guava/pull/2567

Shall I publish a RC tomorrow after merging #646 ?

-1 for merging #646 unless it's safe with unexpected system properties
-1 for RC for #646. If this is the only bug / improvement, I suggest we push out 2.1.0 final, followed by 2.1.1 that contains the improvement. While I like the improvement, it does not strike me as something that needs another release candidate.

Given that we don't have more feedback to investigate, I suggest to set the final release to Monday. Good day to release :)

I think we should merge #646 before releasing a final version. We already got several reported issues that were caused by old Java versions and I expect that quite a lot of people might not have updated their JDK in years.

Otherthan that, I agree that we do not need a release candidate for this.

Oh oops I indeed did not mean RC, but the final release :p

So lets merge #646 when the issue is fixed and then publish `2.1.0` on monday :tada:

I will tweak the code before the end of the week-end, I was short on time these past two week.

The final version needs to be exactly the same as the last RC. That's the idea of "release candidates" :)

Essentially, the options are:

a) publish 2.1.0 final now, include the improvement in 2.1.1 (no rc needed, after 2.1.0 is out we won't be releasing candidates for 2.x versions)
b) wait for fixes in the improvement, merge it, publish 2.1.0-rc-2, wait some time (for example, a day), publish 2.1.0 final

@raphw says it's important to include the fix, so let's do option b)

It is not strictly required for 1 very small bug fix to create a whole new RC with the accompanying waiting time. Other teams have also published their latest RC with just a couple of bug fixes immediately as final release, so I think we can safely release 2.1.0 with #646 on monday.

The fact that other teams do it does not mean that it's right :)

We want to be in the position that we can always say that: "the RC is exactly the final release". This helps convincing teams to use RCs.

Of course nothing can stop you from publishing final with any number of fixes you want. However, it is simply the matter of good practice to follow version conventions and avoid making exceptions for no good reason.

Publishing final version with patches on top of last RC is sloppy. The reason one might want to do it is when the release process is manual / hard. It's not the case for Mockito, where entire pipeline is automated!!! :D

I agree with you that we should **not** push this to the final release but I really think that we should merge #646 before going final. Therefore, I suggest a second release candidate instead of a release on Monday. Let's please do this right. Isn't this what RCs are for? Doing so, we can also merge #648.

The problem addressed by #646 was reported at least 6 times. Many developers do not even mean to run old Java versions but have some outdated JDK bundled with their IDE. This means that tests run using Maven but not in, for example, Eclipse, what is difficult to debug and will most likely cause frustration among some early adopters of Mockito 2. In the end, I have spent some hours myself to investigate reported issues of this origin. If we had thought of something like @bric3's solution earlier, I am sure we would have done this right away and I was very happy to see this PR. I am also sure this automatic debugging help will free us some time once Mockito 2 hits the mainstream and more people run into this problem.

As for #648, I think this can make quite a difference in how the Mockito 2 release is perceived in the public. A lot of people have already implemented their own stream-aware "smart null" answers or Java 8-adapters - thanks to the great Mockito API making this so easy - and most of our other changes are internal clean-ups and and do not really change user experience. With adding support for mocking final types and methods, we are delivering something new and exciting, something that people will speak about. Without such a feature, we are risking to suffer the Java 7-phenomenon where very few people bothered to update despite the significant changes made in the OpenJDK. In the end, to a user updating is a risk and additional work; so why bother if there is nothing in for you? In this perspective, Java 8 was an entirely different story. And _true_ final mocks do not really exist in the Java-space yet, in this respect we have built something that was never done before! I am sure people will want to try this feature out what hopefully motivates more folks to an upgrade.

Given that we have 2 PRs that only need to be merged with features that are both helping migration and adding some fuzz about the upcoming release, I am strictly against going for a final version on Monday. Let's pick this low hanging fruit. Also, from a marketing perspective, releasing Mockito 2.1 on Monday and Mockito 2.2-RC on Tuesday, I do not think this would sell well.

I wish I had time to do all this before the RC was published but I was too busy during JavaZone and I had to catch up some sleep before getting everything I wanted ready. But now we have waited so long, let's delay the final version for just a little bit more. Isn't anticipation the greatest joy anyways? 

> The problem addressed by #646 was reported at least 6 times. 

I totally agree. Let's make java8 warning a part of 2.1.0. All I want is to follow good practices of versioning and RC.

> releasing Mockito 2.1 on Monday and Mockito 2.2-RC on Tuesday, I do not think this would sell well.

We don't publish RCs for _compatible_ versions. We don't plan RCs for 2.2, 2.3, 2.12.32, they are compatible versions and don't need RCs.

> And true final mocks do not really exist in the Java-space yet, in this respect we have built something that was never done before!

LOVE IT!!!!

> As for #648, I think this can make quite a difference in how the Mockito 2 release is perceived in the public.

That's really cool :) Caveats:
- it's late
- the feature is not ready (assuming the code is ok, documentation? release notes?)
- the feature is a breaking change due to extra dependencies

I'm not sure why you push so hard to include it in 2.1.0. You can spend more time on this feature, make it great, compatible, well documented, push it with 2.2.0 few weeks from now, include buzz, promo, etc. You will achieve **exactly** the same results community-wise. Actually, I think it is even better to spread good features over time to demonstrate that Mockito continually improves. Community will be adopting Mockito 2 slowly, it will take years. Some projects will stay on 1.10 forever :)

I'm curious:
- how much time is needed to make the feature is ready?
- how much time is needed to make the feature backwards compatible?

@raphw, I'm very happy you work on this and I totally admire your passion to improve Mockito. Let's build a plan and make it work. This feature is too late for 2.1.0. Last minute, unannounced big features, in the RC phase - this is just not a good idea. We've been planning the release for months, inline mocks never appeared on the checklists.

Long story short, we don't need to block 2.1.0 to make this available in 2.x and make it an awesome, neat feature!!! :)

As I said before, the feature is ready: it passes all of Mockito's unit tests when it is activated and we even added this to the Travis build to be constantly verified. At the same time, the feature guarantees to not break anything.

I want to merge this now as there is nothing left to do, this is why the feature is a PR ready to be merged and not a private branch. If it is not included in 2.1, I can live with this but I find this a missed opportunity. From my perspective, this feature is good to go and I do not plan to spend more time on it, even if it is not included in 2.1. I can only improve it once people started using it and give me feedback.

I already explained why I think it would be better to include it in 2.1 from a marketing perspective. Other than that, the feature can be added in 2.2 but in the meantime, it will just sit there with nothing changing; worst case some rebasements onto head.

Effectively, what you suggests means therefore that we release 2.1.Final now and 2.2.Final in a few weeks (?). 2.2.Final will be the exact same jar as 2.1.Final would be with this feature merged. I simply do not see how this would improve anything.

Usually following process is is good, but some time following guts is nice too. I second @raphw to include the mockmaker, it's opt-in. 

I agree final mock never appeared on the radar here, but the topic was discussed and investigated more than a year ago, by me and especially Rafael who, in the end, achieved this alone. That's a fantastic work @raphw !!!

Including this is an opportunity. We already included incubating API before, I don't see why we shouldn't include it in 2.1.0 (and as such an 2.1.0-RC.x ) as well.

Thank you guys challenging me and the good practices. We should be challenging one another and keep growing as technical leaders. Discussions like those are the core of collaborative software engineering. Let's enjoy them and never feel discouraged! I find you guys the best crowd I worked with in a while, and @raphw, you did pull off a feature nobody yet did in the history of mocking :)

About the feature: it's too late to include it in the 2.1.0 final. It's great work and it must be included in Mockito. This feature is incompatible in the current form because of new dependencies. 

I will continue pushing Mockito team to follow decent engineering practices:
- when RC is published, subsequent RCs are triggered for bugfixes required to get the release out
- subsequent RCs are not used to ship new features, especially when they include breaking changes
- no sudden big changes, midnight before major version release ;)
- favoring incremental improvements, for better stability

Mockito is the product that has 2M users, it deserves thoughtful and rigorous following of decent engineering practices. Of course exceptions are necessary, but for good reasons!

> Effectively, what you suggests means therefore that we release 2.1.Final now and 2.2.Final in a few weeks (?). 2.2.Final will be the exact same jar as 2.1.Final would be with this feature merged. I simply do not see how this would improve anything.

We can merge and publish version 2.2.0 even next day after the release, so long the feature is ready & compatible. I haven't reviewed the PR thoroughly yet, anyone else did? Does it have user-facing documentation (e.g. how can I opt-in for the feature, what can I expect?). Is it utterly and completely safe for default behavior?

> Is it utterly and completely safe for default behavior?

**YES**, it uses the plugin loader for that, people that want to try final class mocks will have to explicitly make a file in the folder `mockito-extensions`.

> Does it have user-facing documentation (e.g. how can I opt-in for the feature, what can I expect?)

See [`Mockito` class](https://github.com/mockito/mockito/pull/648/files#diff-ddbc0d47e06a589778a6efe24c5d71d3R1220)

> I haven't reviewed the PR thoroughly yet, anyone else did?

**YES**, I did

> - subsequent RCs are not used to ship new features, especially when they include breaking changes

Adding a dependency is not a breaking change. However for other reasons I think mockito should avoid relying on that dependency. **EDIT :** manually shadowed.

> - no sudden big changes, midnight before major version release ;)

For such a change, actually an opt-in feature, I think we should release another release candidate. (We will have another RC anyway due to the java version warning.)

@bric3, shouting not needed :) Thank you for the answers! It's really helpful.

The documentation looks nice, I'll make some edits in the branch.

Adding new dependency is backwards incompatible. I will write later why :)

I'm +1 to including it in RC because:
- I'm confident about the quality and documentation
- It's an opt-in feature
- New dependency is shadowed
- The other new dependency (agent) is a part of ByteBuddy, so I'm willing to compromise the backwards compatibility of the RCs this time.

I will write a comment to PR with few things I want to get done before it's merged.

Great work, thank you!

Adding new dependency is a breaking change: https://www.linkedin.com/pulse/sofware-craftsmanship-adding-new-dependency-breaking-change-faber :)

I don't think I fully agree with your article. In very technical terms, yes it is a breaking change. However, claiming that it breaks builds is imo a stretch in this particular case. The dependency is very small, has almost no new versions and looking at the release schedule, only 3 minor versions were available longer than a week. Therefore the likelihood of having a dependency conflict (even in minor range) is close to zero (if not zero). Therefore I think it is an exaggeration to claim it is something to worry about.

I would also not like to point to your thoughtpiece in the documentation. I do acknowledge your point of view, but I simply do not share it. Therefore I would like to refrain from putting it in the official documentation, since that would indicate it is shared by the whole Mockito dev team.

The article does not discuss the probability of failure. It simply describes facts. Change that can break clients is by definition "incompatible". The team can still decide to ship such change, as we are doing :)

I respect your POV. Write your own article!

I intend to link to the article. However, I would love to link to your article, too. Mockito needs more traction and publicity, strong opinions and public discussions is good for us all.

I'm serious, write the article!

@szczepiq 
I don't see a problem of adding a new dependency since the major version of mockito changed. Mockito v1.x has dependencies to hamcrest and objenesis whereas v2.x relies one byte-buddy and objenesis, so an other dependency don't make it worse. 

The problem you describe in your article is IMO not the added dependency that breaks the build, that can also happen if a library updates to a new version of a dependency . I think the root cause is the fact that you client relies on library (read mockito) version that has no final/GA release. If they use non final version they should be aware of changes. For the same reason many teams have a policy that only allows to use final releases. 

I don't agree with these facts :

> Adding new dependency is a breaking change
> - New dependency is not present in corporate repo of one of your clients. His build breaks.

This argument completely disreagard dependency management tool like gradle or maven. Mockito is only distributed on bintray and maven central that are exactly made for getting those transitive dependencies.

> - New dependency is not allowed in corporate repo of your client (for example, due to poor reputation or security concerns). The client cannot use the new version.

With such policies, the importing library update itself is subject to these restrictions, and therefore updates are not allowed anyway until reviews approves changes of the importing library. This will happen regardless of the dependency being an external jar or being shaded.

e.g. license concerns won't be elimitated if the classes are just shaded.

> - New dependency is already declared by the client, but with different, incompatible version. Conflict resolution picks up later version causing failures at runtime.
> 
> Updating dependency could be a breaking change

This is indeed true and this is the only valid argument to me. Anyway the scope of this argument is limited because upgrading the importing library too can be in itself a breaking change (according to semver), e.g. mockito 2.x is incompatible with mockito 1.x.
This is indeed a problem with minor versions.

Updating a dependency is indeed something that could be breaking, but so could be the importing library.

@bric3, you're right, my first example in the use case is invalid. Thanks for pointing this out, I'll update the article.

@bric3, @ChristianSchwarz - you're right about the fact that major versions can carry breaking changes. However, including incompatible changes in subsequent release candidate is a poor engineering practice.

Release candidate by definition, is a software that is "ready to release" unless significant bugs are discovered. This means that N+1 release candidate should not have new features, big changes, and most importantly, incompatible changes. Consumers of release candidates need to be able to rely on this rule.

Thoughtful exceptions can always be made by the team, as we are doing :) I'll keep pushing for decent versioning practices. Thanks guys for feedback!!!

@szczepiq not there's still two items in #123, I think they can be safely removed, as we won't remove the serialVersionUid, and unincubated API have either _hatched out_ or stil incubating.

Also on mockito javadoc there's link that point to an old website (like https://monkeyisland.pl/2009/01/13/subclass-and-override-vs-partial-mocking-vs-refactoring), I think we should only point to mockito wiki or github repo, not external website that can disappear anytime.

Done. 

> I think we should only point to mockito wiki or github repo

+1

I think we can publish on monday. The release seems very stable and we have yet another successful upgrade, this time at RxJava: https://github.com/ReactiveX/RxJava/pull/4636

+1. THANKS for great work :DDDD Pleasure doing projects together!!!

Great idea to migrate RxJava!

Published, w00h00. Upgrade today :tada: 


Pull request review comments:
Why would this be abstract? It has a private constructor anyways.

To keep it consistent, let's make these constant Strings too.

Space after `if`?

I would reverse this, to make the conditional equivalent to the name of the method. Very small nit though, but I had to think about it for a second :smile: 

It seems that these lines are untested. Are we able to come up with a test for this? E.g. why would we reorder these values.

replace 'visible' with 'non-private'? Users might not know what 'visible' is :)

AFAIR, StringJoiner always adds next line for each String argument. This means that if java is < 45, there will be extra EOLs. Perhaps it's fine, just wanted FYI.

This is a fantastic improvement, very Mockito-style :D We always strive to make the exception messages better, even a notch better. In the long run all those small improvements create a compound effect. We'll have fantastic error handling, crisp and clear exception messages and happy users!!!

FYI: Java 9 changes the standard version String and possibly other standard system properties. Let's make sure it works on Java 9 :)

Let's make sure this is always safe, regardless of the content of the system properties. Those properties are mutable AFAIK, and/or might change/be different with future versions of java or perhaps even jvm/jdk vendor.

Otherwise, love this PR :)

Yes I wanted an extra empty line before platform information (if the runtime is a java 8 version below update 45)

ok

At the moment this shouldn't be a problem. the parsing targets Java 8 runtime

http://openjdk.java.net/jeps/223

Because they are wrong in the message ;) 
The type to mock is not the class that the mockmaker created ;)

yes i forgot that, I was a bit in hurry for this PR, and wanted to push on the remote ;)
This was still a work in progress ;)

It's a usual practice to disallow instantiation.

Yes true, but that is what the private constructor guarantees too right? But leaving it in is fine too :)


Patch that resolved the issue:
diff --git a/src/main/java/org/mockito/internal/creation/bytebuddy/ByteBuddyMockMaker.java b/src/main/java/org/mockito/internal/creation/bytebuddy/ByteBuddyMockMaker.java
index 907ebd0276..9d165f6899 100644
--- a/src/main/java/org/mockito/internal/creation/bytebuddy/ByteBuddyMockMaker.java
+++ b/src/main/java/org/mockito/internal/creation/bytebuddy/ByteBuddyMockMaker.java
@@ -1,19 +1,18 @@
 package org.mockito.internal.creation.bytebuddy;
 
+import static org.mockito.internal.util.StringJoiner.join;
+import java.lang.reflect.Modifier;
 import org.mockito.exceptions.base.MockitoException;
 import org.mockito.internal.InternalMockHandler;
 import org.mockito.internal.configuration.plugins.Plugins;
 import org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.MockAccess;
 import org.mockito.internal.creation.instance.Instantiator;
+import org.mockito.internal.util.Platform;
 import org.mockito.invocation.MockHandler;
 import org.mockito.mock.MockCreationSettings;
 import org.mockito.mock.SerializableMode;
 import org.mockito.plugins.MockMaker;
 
-import java.lang.reflect.Modifier;
-
-import static org.mockito.internal.util.StringJoiner.join;
-
 public class ByteBuddyMockMaker implements MockMaker {
 
     private final CachingMockBytecodeGenerator cachingMockBytecodeGenerator;
@@ -37,8 +36,8 @@ public <T> T createMock(MockCreationSettings<T> settings, MockHandler handler) {
         } catch (ClassCastException cce) {
             throw new MockitoException(join(
                     "ClassCastException occurred while creating the mockito mock :",
-                    "  class to mock : " + describeClass(mockedProxyType),
-                    "  created class : " + describeClass(settings.getTypeToMock()),
+                    "  class to mock : " + describeClass(settings.getTypeToMock()),
+                    "  created class : " + describeClass(mockedProxyType),
                     "  proxy instance class : " + describeClass(mockInstance),
                     "  instance creation by : " + instantiator.getClass().getSimpleName(),
                     "",
@@ -51,7 +50,11 @@ public <T> T createMock(MockCreationSettings<T> settings, MockHandler handler) {
     }
 
     <T> Class<T> createProxyClass(MockFeatures<T> mockFeatures) {
-        return cachingMockBytecodeGenerator.get(mockFeatures);
+        try {
+            return cachingMockBytecodeGenerator.get(mockFeatures);
+        } catch (Exception bytecodeGenerationFailed) {
+            throw prettifyFailure(mockFeatures, bytecodeGenerationFailed);
+        }
     }
 
 
@@ -71,6 +74,27 @@ private <T> T ensureMockIsAssignableToMockedType(MockCreationSettings<T> setting
         return typeToMock.cast(mock);
     }
 
+    private RuntimeException prettifyFailure(MockFeatures<?> mockFeatures, Exception generationFailed) {
+        if (Modifier.isPrivate(mockFeatures.mockedType.getModifiers())) {
+            throw new MockitoException(join(
+                    "Mockito cannot mock this class: " + mockFeatures.mockedType + ".",
+                    "Most likely it is a private class that is not visible by Mockito",
+                    ""
+            ), generationFailed);
+        }
+        throw new MockitoException(join(
+                "Mockito cannot mock this class: " + mockFeatures.mockedType,
+                "",
+                "Mockito can only non-private & non-final classes.",
+                "If you're not sure why you're getting this error, please report to the mailing list.",
+                "",
+                Platform.isJava8BelowUpdate45() ? "Java 8 early builds have bugs that were addressed in Java 1.8.0_45, please update your JDK!\n" : "",
+                Platform.describe(),
+                "",
+                "Underlying exception : " + generationFailed
+        ), generationFailed);
+    }
+
     private static String describeClass(Class<?> type) {
         return type == null ? "null" : "'" + type.getCanonicalName() + "', loaded by classloader : '" + type.getClassLoader() + "'";
     }
diff --git a/src/main/java/org/mockito/internal/creation/bytebuddy/CachingMockBytecodeGenerator.java b/src/main/java/org/mockito/internal/creation/bytebuddy/CachingMockBytecodeGenerator.java
index 1093c37c87..461dec6168 100644
--- a/src/main/java/org/mockito/internal/creation/bytebuddy/CachingMockBytecodeGenerator.java
+++ b/src/main/java/org/mockito/internal/creation/bytebuddy/CachingMockBytecodeGenerator.java
@@ -1,12 +1,9 @@
 package org.mockito.internal.creation.bytebuddy;
 
-import org.mockito.exceptions.base.MockitoException;
-
 import java.lang.ref.Reference;
 import java.lang.ref.ReferenceQueue;
 import java.lang.ref.SoftReference;
 import java.lang.ref.WeakReference;
-import java.lang.reflect.Modifier;
 import java.net.URL;
 import java.net.URLClassLoader;
 import java.util.Collections;
@@ -15,8 +12,6 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 
-import static org.mockito.internal.util.StringJoiner.join;
-
 class CachingMockBytecodeGenerator extends ReferenceQueue<ClassLoader> {
 
     private static final ClassLoader BOOT_LOADER = new URLClassLoader(new URL[0], null);
@@ -86,7 +81,7 @@ Class<?> getOrGenerateMockClass(MockFeatures<?> features) {
                 synchronized (features.mockedType) {
                     generatedMockClass = getMockClass(mockKey);
                     if(generatedMockClass == null) {
-                        generatedMockClass = generate(features);
+                        generatedMockClass = generator.generateMockClass(features);
                         generatedClassCache.put(mockKey, weak ? new WeakReference<Class<?>>(generatedMockClass) : new SoftReference<Class<?>>(generatedMockClass));
                     }
                 }
@@ -94,33 +89,6 @@ Class<?> getOrGenerateMockClass(MockFeatures<?> features) {
             return generatedMockClass;
         }
 
-        private <T> Class<? extends T> generate(MockFeatures<T> mockFeatures) {
-            try {
-                return generator.generateMockClass(mockFeatures);
-            } catch (Exception bytecodeGenerationFailed) {
-                throw prettifyFailure(mockFeatures, bytecodeGenerationFailed);
-            }
-        }
-
-        private RuntimeException prettifyFailure(MockFeatures<?> mockFeatures, Exception generationFailed) {
-            if (Modifier.isPrivate(mockFeatures.mockedType.getModifiers())) {
-                throw new MockitoException(join(
-                        "Mockito cannot mock this class: " + mockFeatures.mockedType + ".",
-                        "Most likely it is a private class that is not visible by Mockito",
-                        ""
-                ), generationFailed);
-            }
-            throw new MockitoException(join(
-                    "Mockito cannot mock this class: " + mockFeatures.mockedType,
-                    "",
-                    "Mockito can only mock visible & non-final classes.",
-                    "If you're not sure why you're getting this error, please report to the mailing list.",
-                    "",
-                    "Underlying exception : " + generationFailed),
-                    generationFailed
-            );
-        }
-
         // should be stored as a weak reference
         private static class MockKey<T> {
             private final String mockedType;
diff --git a/src/main/java/org/mockito/internal/util/Platform.java b/src/main/java/org/mockito/internal/util/Platform.java
new file mode 100644
index 0000000000..c899b20dab
--- /dev/null
+++ b/src/main/java/org/mockito/internal/util/Platform.java
@@ -0,0 +1,64 @@
+package org.mockito.internal.util;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public abstract class Platform {
+
+    private static final Pattern JAVA_8_RELEASE_VERSION_SCHEME = Pattern.compile("1\\.8\\.0_(\\d+)(?:-ea)?(?:-b\\d+)?");
+    private static final Pattern JAVA_8_DEV_VERSION_SCHEME = Pattern.compile("1\\.8\\.0b\\d+_u(\\d+)");
+    public static final String JAVA_VERSION = System.getProperty("java.specification.version");
+    public static final String JVM_VERSION = System.getProperty("java.runtime.version");
+    public static final String JVM_VENDOR = System.getProperty("java.vm.vendor");
+    public static final String JVM_VENDOR_VERSION = System.getProperty("java.vm.version");
+    public static final String JVM_NAME = System.getProperty("java.vm.name");
+    public static final String JVM_INFO = System.getProperty("java.vm.info");
+    public static final String OS_NAME = System.getProperty("os.name");
+    public static final String OS_VERSION = System.getProperty("os.version");
+
+    private Platform() {}
+
+    public static String describe() {
+        return String.format("Java               : %s\n" +
+                             "JVM vendor name    : %s\n" +
+                             "JVM vendor version : %s\n" +
+                             "JVM name           : %s\n" +
+                             "JVM version        : %s\n" +
+                             "JVM info           : %s\n" +
+                             "OS name            : %s\n" +
+                             "OS version         : %s\n",
+                             JAVA_VERSION,
+                             JVM_VENDOR,
+                             JVM_VENDOR_VERSION,
+                             JVM_NAME,
+                             JVM_VERSION,
+                             JVM_INFO,
+                             OS_NAME,
+                             OS_VERSION);
+    }
+
+    public static boolean isJava8BelowUpdate45() {
+        return isJava8BelowUpdate45(JVM_VERSION);
+    }
+
+    static boolean isJava8BelowUpdate45(String jvmVersion) {
+        Matcher matcher = JAVA_8_RELEASE_VERSION_SCHEME.matcher(jvmVersion);
+        if (matcher.matches()) {
+            int update = Integer.parseInt(matcher.group(1));
+            return update < 45;
+        }
+
+        matcher = JAVA_8_DEV_VERSION_SCHEME.matcher(jvmVersion);
+        if (matcher.matches()) {
+            int update = Integer.parseInt(matcher.group(1));
+            return update < 45;
+        }
+
+        matcher = Pattern.compile("1\\.8\\.0-b\\d+").matcher(jvmVersion);
+        if (matcher.matches()) {
+            return true;
+        }
+
+        return false;
+    }
+}


Test patch:
diff --git a/src/test/java/org/mockito/internal/util/PlatformTest.java b/src/test/java/org/mockito/internal/util/PlatformTest.java
new file mode 100644
index 0000000000..f146666620
--- /dev/null
+++ b/src/test/java/org/mockito/internal/util/PlatformTest.java
@@ -0,0 +1,98 @@
+package org.mockito.internal.util;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.Test;
+
+public class PlatformTest {
+
+    @Test
+    public void const_are_initialized_from_system_properties() {
+        assertThat(Platform.JVM_VERSION).isEqualTo(System.getProperty("java.runtime.version"));
+        assertThat(Platform.JVM_INFO).isEqualTo(System.getProperty("java.vm.info"));
+        assertThat(Platform.JVM_NAME).isEqualTo(System.getProperty("java.vm.name"));
+        assertThat(Platform.JVM_VENDOR).isEqualTo(System.getProperty("java.vm.vendor"));
+        assertThat(Platform.JVM_VENDOR_VERSION).isEqualTo(System.getProperty("java.vm.version"));
+    }
+
+    @Test
+    public void should_parse_open_jdk_string() {
+        // Given
+        // Sources :
+        //  - http://www.oracle.com/technetwork/java/javase/versioning-naming-139433.html
+        //  - http://www.oracle.com/technetwork/java/javase/jdk7-naming-418744.html
+        //  - http://www.oracle.com/technetwork/java/javase/jdk8-naming-2157130.html
+        //  - http://stackoverflow.com/questions/35844985/how-do-we-get-sr-and-fp-of-ibm-jre-using-java
+        //  - http://www.ibm.com/support/knowledgecenter/SSYKE2_6.0.0/com.ibm.java.doc.user.win32.60/user/java_version_check.html
+        Map<String, Boolean> versions = new HashMap<String, Boolean>() {{
+            put("1.8.0_92-b14", false);
+            put("1.8.0-b24", true);
+            put("1.8.0_5", true);
+            put("1.8.0b5_u44", true);
+            put("1.8.0b5_u92", false);
+            put("1.7.0_4", false);
+            put("1.4.0_03-b04", false);
+            put("1.4.0_03-ea-b01", false);
+            put("pxi3270_27sr4-20160303_03 (SR4)", false);
+            put("pwi3260sr11-20120412_01 (SR11)", false);
+        }};
+
+        assertPlatformParsesCorrectlyVariousVersionScheme(versions);
+    }
+
+    @Test
+    public void should_parse_open_jdk9_string() {
+        // The tested method targets Java 8 but should be able to parse other Java version numbers including Java 9
+
+        // Given
+        // Sources :
+        //  - http://openjdk.java.net/jeps/223 (Java 9)
+        //
+        // System Property                 Existing      Proposed
+        // ------------------------------- ------------  --------
+        // Early Access
+        // java.runtime.version            1.9.0-ea-b73  9-ea+73
+        // java.vm.version                 1.9.0-ea-b73  9-ea+73
+        // java.specification.version      1.9           9
+        // java.vm.specification.version   1.9           9
+        //
+        // Major (GA)
+        // java.version                    1.9.0         9
+        // java.runtime.version            1.9.0-b100    9+100
+        // java.vm.version                 1.9.0-b100    9+100
+        // java.specification.version      1.9           9
+        // java.vm.specification.version   1.9           9
+        //
+        // Minor #1 (GA)
+        // java.version                    1.9.0_20      9.1.2
+        // java.runtime.version            1.9.0_20-b62  9.1.2+62
+        // java.vm.version                 1.9.0_20-b62  9.1.2+62
+        // java.specification.version      1.9           9
+        // java.vm.specification.version   1.9           9
+        //
+        // Security #1 (GA)
+        // java.version                    1.9.0_5       9.0.1
+        // java.runtime.version            1.9.0_5-b20   9.0.1+20
+        // java.vm.version                 1.9.0_5-b20   9.0.1+20
+        // java.specification.version      1.9           9
+        // java.vm.specification.version   1.9           9
+        //
+        Map<String, Boolean> versions = new HashMap<String, Boolean>() {{
+            put("9-ea+73", false);
+            put("9+100", false);
+            put("9.1.2+62", false);
+            put("9.0.1+20", false);
+        }};
+
+        assertPlatformParsesCorrectlyVariousVersionScheme(versions);
+    }
+
+
+    private void assertPlatformParsesCorrectlyVariousVersionScheme(Map<String, Boolean> versions) {
+        for (Map.Entry<String, Boolean> version : versions.entrySet()) {
+            assertThat(Platform.isJava8BelowUpdate45(version.getKey())).describedAs(version.getKey())
+                                                                       .isEqualTo(version.getValue());
+        }
+    }
+}